mirror of
https://github.com/esphome/esphome.git
synced 2024-12-12 14:57:34 +01:00
Merge branch 'dev' into optolink
This commit is contained in:
commit
777dcd8d34
@ -1,17 +1,13 @@
|
||||
{
|
||||
"name": "ESPHome Dev",
|
||||
"image": "ghcr.io/esphome/esphome-lint:dev",
|
||||
"postCreateCommand": [
|
||||
"script/devcontainer-post-create"
|
||||
],
|
||||
"postCreateCommand": ["script/devcontainer-post-create"],
|
||||
"containerEnv": {
|
||||
"DEVCONTAINER": "1"
|
||||
"DEVCONTAINER": "1",
|
||||
"PIP_BREAK_SYSTEM_PACKAGES": "1",
|
||||
"PIP_ROOT_USER_ACTION": "ignore"
|
||||
},
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
"-e",
|
||||
"ESPHOME_DASHBOARD_USE_PING=1"
|
||||
],
|
||||
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
|
||||
"appPort": 6052,
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
@ -24,7 +20,7 @@
|
||||
// cpp
|
||||
"ms-vscode.cpptools",
|
||||
// editorconfig
|
||||
"editorconfig.editorconfig",
|
||||
"editorconfig.editorconfig"
|
||||
],
|
||||
"settings": {
|
||||
"python.languageServer": "Pylance",
|
||||
|
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -221,6 +221,29 @@ jobs:
|
||||
id: set-matrix
|
||||
run: echo "matrix=$(ls tests/test*.yaml | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
|
||||
|
||||
validate-tests:
|
||||
name: Validate YAML test ${{ matrix.file }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
- compile-tests-list
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
file: ${{ fromJson(needs.compile-tests-list.outputs.matrix) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Run esphome config ${{ matrix.file }}
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
esphome config ${{ matrix.file }}
|
||||
|
||||
compile-tests:
|
||||
name: Run YAML test ${{ matrix.file }}
|
||||
runs-on: ubuntu-latest
|
||||
@ -234,6 +257,7 @@ jobs:
|
||||
- pytest
|
||||
- pyupgrade
|
||||
- compile-tests-list
|
||||
- validate-tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 2
|
||||
|
24
.github/workflows/needs-docs.yml
vendored
Normal file
24
.github/workflows/needs-docs.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Needs Docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for needs-docs label
|
||||
uses: actions/github-script@v6.4.1
|
||||
with:
|
||||
script: |
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
const needsDocs = labels.find(label => label.name === 'needs-docs');
|
||||
if (needsDocs) {
|
||||
core.setFailed('Pull request needs docs');
|
||||
}
|
@ -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.10.0
|
||||
rev: 23.10.1
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@ -11,7 +11,7 @@ repos:
|
||||
- --quiet
|
||||
files: ^((esphome|script|tests)/.+)?[^/]+\.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
rev: 6.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
|
@ -77,6 +77,7 @@ esphome/components/dashboard_import/* @esphome/core
|
||||
esphome/components/debug/* @OttoWinter
|
||||
esphome/components/delonghi/* @grob6000
|
||||
esphome/components/dfplayer/* @glmnet
|
||||
esphome/components/dfrobot_sen0395/* @niklasweber
|
||||
esphome/components/dht/* @OttoWinter
|
||||
esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
@ -151,6 +152,7 @@ esphome/components/key_provider/* @ssieb
|
||||
esphome/components/kuntze/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @regevbr @sebcaps
|
||||
esphome/components/ld2420/* @descipher
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/libretiny/* @kuba2k2
|
||||
esphome/components/libretiny_pwm/* @kuba2k2
|
||||
@ -182,6 +184,7 @@ esphome/components/mcp9808/* @k7hpn
|
||||
esphome/components/md5/* @esphome/core
|
||||
esphome/components/mdns/* @esphome/core
|
||||
esphome/components/media_player/* @jesserockz
|
||||
esphome/components/micronova/* @jorre05
|
||||
esphome/components/microphone/* @jesserockz
|
||||
esphome/components/mics_4514/* @jesserockz
|
||||
esphome/components/midea/* @dudanov
|
||||
@ -217,7 +220,7 @@ esphome/components/optolink/* @j0ta29
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pid/* @OttoWinter
|
||||
|
@ -43,10 +43,11 @@ RUN \
|
||||
zlib1g-dev=1:1.2.13.dfsg-1 \
|
||||
libjpeg-dev=1:2.1.5-2 \
|
||||
libfreetype-dev=2.12.1+dfsg-5 \
|
||||
libssl-dev=3.0.11-1~deb12u1 \
|
||||
libssl-dev=3.0.11-1~deb12u2 \
|
||||
libffi-dev=3.4.4-1 \
|
||||
cargo=0.66.0+ds1-1 \
|
||||
pkg-config=1.8.1-1; \
|
||||
gcc-arm-linux-gnueabihf=4:12.2.0-3; \
|
||||
fi; \
|
||||
rm -rf \
|
||||
/tmp/* \
|
||||
@ -100,6 +101,10 @@ ENV USERNAME="" PASSWORD=""
|
||||
# Expose the dashboard to Docker
|
||||
EXPOSE 6052
|
||||
|
||||
# Run healthcheck (heartbeat)
|
||||
HEALTHCHECK --interval=30s --timeout=30s \
|
||||
CMD curl --fail http://localhost:6052/version -A "HealthCheck" || exit 1
|
||||
|
||||
COPY docker/docker_entrypoint.sh /entrypoint.sh
|
||||
|
||||
# The directory the user should mount their configuration files to
|
||||
|
@ -18,6 +18,8 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_EVENT,
|
||||
CONF_TAG,
|
||||
CONF_ON_CLIENT_CONNECTED,
|
||||
CONF_ON_CLIENT_DISCONNECTED,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
@ -87,6 +89,12 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.Required(CONF_KEY): validate_encryption_key,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@ -116,6 +124,20 @@ async def to_code(config):
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
if CONF_ON_CLIENT_CONNECTED in config:
|
||||
await automation.build_automation(
|
||||
var.get_client_connected_trigger(),
|
||||
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
|
||||
config[CONF_ON_CLIENT_CONNECTED],
|
||||
)
|
||||
|
||||
if CONF_ON_CLIENT_DISCONNECTED in config:
|
||||
await automation.build_automation(
|
||||
var.get_client_disconnected_trigger(),
|
||||
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
|
||||
config[CONF_ON_CLIENT_DISCONNECTED],
|
||||
)
|
||||
|
||||
if encryption_config := config.get(CONF_ENCRYPTION):
|
||||
decoded = base64.b64decode(encryption_config[CONF_KEY])
|
||||
cg.add(var.set_noise_psk(list(decoded)))
|
||||
|
@ -217,6 +217,8 @@ message DeviceInfoResponse {
|
||||
string friendly_name = 13;
|
||||
|
||||
uint32 voice_assistant_version = 14;
|
||||
|
||||
string suggested_area = 16;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
|
@ -32,9 +32,9 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
#if defined(USE_API_PLAINTEXT)
|
||||
helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
#elif defined(USE_API_NOISE)
|
||||
helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
#else
|
||||
#error "No frame helper defined"
|
||||
#endif
|
||||
@ -42,14 +42,16 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
void APIConnection::start() {
|
||||
this->last_traffic_ = millis();
|
||||
|
||||
APIError err = helper_->init();
|
||||
APIError err = this->helper_->init();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
|
||||
errno);
|
||||
return;
|
||||
}
|
||||
client_info_ = helper_->getpeername();
|
||||
helper_->set_log_info(client_info_);
|
||||
this->client_info_ = helper_->getpeername();
|
||||
this->client_peername_ = this->client_info_;
|
||||
this->helper_->set_log_info(this->client_info_);
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
@ -58,6 +60,11 @@ APIConnection::~APIConnection() {
|
||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
if (voice_assistant::global_voice_assistant->get_api_connection() == this) {
|
||||
voice_assistant::global_voice_assistant->client_subscription(this, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
@ -68,7 +75,7 @@ void APIConnection::loop() {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str());
|
||||
ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", this->client_combined_info_.c_str());
|
||||
return;
|
||||
}
|
||||
if (this->next_close_) {
|
||||
@ -78,24 +85,26 @@ void APIConnection::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
APIError err = helper_->loop();
|
||||
APIError err = this->helper_->loop();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
|
||||
api_error_to_str(err), errno);
|
||||
return;
|
||||
}
|
||||
ReadPacketBuffer buffer;
|
||||
err = helper_->read_packet(&buffer);
|
||||
err = this->helper_->read_packet(&buffer);
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
// pass
|
||||
} else if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
|
||||
} else if (err == APIError::CONNECTION_CLOSED) {
|
||||
ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str());
|
||||
ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
|
||||
errno);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
@ -115,7 +124,7 @@ void APIConnection::loop() {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (keepalive * 5) / 2) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str());
|
||||
ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str());
|
||||
}
|
||||
} else if (now - this->last_traffic_ > keepalive) {
|
||||
ESP_LOGVV(TAG, "Sending keepalive PING...");
|
||||
@ -169,7 +178,7 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s requested disconnected", this->client_combined_info_.c_str());
|
||||
this->next_close_ = true;
|
||||
DisconnectResponse resp;
|
||||
return resp;
|
||||
@ -946,14 +955,17 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIConnection::request_voice_assistant(const VoiceAssistantRequest &msg) {
|
||||
if (!this->voice_assistant_subscription_)
|
||||
return false;
|
||||
|
||||
return this->send_voice_assistant_request(msg);
|
||||
void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
|
||||
}
|
||||
}
|
||||
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.error) {
|
||||
voice_assistant::global_voice_assistant->failed_to_start();
|
||||
return;
|
||||
@ -966,6 +978,10 @@ void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &ms
|
||||
};
|
||||
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
|
||||
if (voice_assistant::global_voice_assistant != nullptr) {
|
||||
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
voice_assistant::global_voice_assistant->on_event(msg);
|
||||
}
|
||||
}
|
||||
@ -1045,12 +1061,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin
|
||||
}
|
||||
|
||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")";
|
||||
this->helper_->set_log_info(client_info_);
|
||||
this->client_info_ = msg.client_info;
|
||||
this->client_peername_ = this->helper_->getpeername();
|
||||
this->client_combined_info_ = this->client_info_ + " (" + this->client_peername_ + ")";
|
||||
this->helper_->set_log_info(this->client_combined_info_);
|
||||
this->client_api_version_major_ = msg.api_version_major;
|
||||
this->client_api_version_minor_ = msg.api_version_minor;
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
|
||||
this->client_peername_.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
@ -1068,9 +1086,9 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||
// bool invalid_password = 1;
|
||||
resp.invalid_password = !correct;
|
||||
if (correct) {
|
||||
ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: Connected successfully", this->client_combined_info_.c_str());
|
||||
this->connection_state_ = ConnectionState::AUTHENTICATED;
|
||||
|
||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
this->send_time_request();
|
||||
@ -1084,6 +1102,7 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
resp.uses_password = this->parent_->uses_password();
|
||||
resp.name = App.get_name();
|
||||
resp.friendly_name = App.get_friendly_name();
|
||||
resp.suggested_area = App.get_area();
|
||||
resp.mac_address = get_mac_address_pretty();
|
||||
resp.esphome_version = ESPHOME_VERSION;
|
||||
resp.compilation_time = App.get_compilation_time();
|
||||
@ -1144,10 +1163,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
return false;
|
||||
if (!this->helper_->can_write_without_blocking()) {
|
||||
delay(0);
|
||||
APIError err = helper_->loop();
|
||||
APIError err = this->helper_->loop();
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(),
|
||||
api_error_to_str(err), errno);
|
||||
return false;
|
||||
}
|
||||
if (!this->helper_->can_write_without_blocking()) {
|
||||
@ -1166,9 +1186,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
if (err != APIError::OK) {
|
||||
on_fatal_error();
|
||||
if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) {
|
||||
ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str());
|
||||
ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str());
|
||||
} else {
|
||||
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno);
|
||||
ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err),
|
||||
errno);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1177,11 +1198,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type)
|
||||
}
|
||||
void APIConnection::on_unauthenticated_access() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_combined_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str());
|
||||
ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_combined_info_.c_str());
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
this->helper_->close();
|
||||
|
@ -126,10 +126,7 @@ class APIConnection : public APIServerConnection {
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override {
|
||||
this->voice_assistant_subscription_ = msg.subscribe;
|
||||
}
|
||||
bool request_voice_assistant(const VoiceAssistantRequest &msg);
|
||||
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override;
|
||||
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
|
||||
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
|
||||
#endif
|
||||
@ -188,6 +185,8 @@ class APIConnection : public APIServerConnection {
|
||||
}
|
||||
bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override;
|
||||
|
||||
std::string get_client_combined_info() const { return this->client_combined_info_; }
|
||||
|
||||
protected:
|
||||
friend APIServer;
|
||||
|
||||
@ -207,6 +206,8 @@ class APIConnection : public APIServerConnection {
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
|
||||
std::string client_info_;
|
||||
std::string client_peername_;
|
||||
std::string client_combined_info_;
|
||||
uint32_t client_api_version_major_{0};
|
||||
uint32_t client_api_version_minor_{0};
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
@ -218,9 +219,6 @@ class APIConnection : public APIServerConnection {
|
||||
uint32_t last_traffic_;
|
||||
bool sent_ping_{false};
|
||||
bool service_call_subscription_{false};
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool voice_assistant_subscription_{false};
|
||||
#endif
|
||||
bool next_close_ = false;
|
||||
APIServer *parent_;
|
||||
InitialStateIterator initial_state_iterator_;
|
||||
|
@ -761,6 +761,10 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->friendly_name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->suggested_area = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -781,6 +785,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(12, this->manufacturer);
|
||||
buffer.encode_string(13, this->friendly_name);
|
||||
buffer.encode_uint32(14, this->voice_assistant_version);
|
||||
buffer.encode_string(16, this->suggested_area);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@ -849,6 +854,10 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
sprintf(buffer, "%" PRIu32, this->voice_assistant_version);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" suggested_area: ");
|
||||
out.append("'").append(this->suggested_area).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
@ -328,6 +328,7 @@ class DeviceInfoResponse : public ProtoMessage {
|
||||
std::string manufacturer{};
|
||||
std::string friendly_name{};
|
||||
uint32_t voice_assistant_version{0};
|
||||
std::string suggested_area{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
@ -111,6 +111,7 @@ void APIServer::loop() {
|
||||
[](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
|
||||
// print disconnection messages
|
||||
for (auto it = new_end; it != this->clients_.end(); ++it) {
|
||||
this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_);
|
||||
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
|
||||
}
|
||||
// resize vector
|
||||
@ -331,30 +332,6 @@ void APIServer::on_shutdown() {
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool APIServer::start_voice_assistant(const std::string &conversation_id, uint32_t flags,
|
||||
const api::VoiceAssistantAudioSettings &audio_settings) {
|
||||
VoiceAssistantRequest msg;
|
||||
msg.start = true;
|
||||
msg.conversation_id = conversation_id;
|
||||
msg.flags = flags;
|
||||
msg.audio_settings = audio_settings;
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->request_voice_assistant(msg))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void APIServer::stop_voice_assistant() {
|
||||
VoiceAssistantRequest msg;
|
||||
msg.start = false;
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->request_voice_assistant(msg))
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (obj->is_internal())
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
@ -83,12 +84,6 @@ class APIServer : public Component, public Controller {
|
||||
void request_time();
|
||||
#endif
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
bool start_voice_assistant(const std::string &conversation_id, uint32_t flags,
|
||||
const api::VoiceAssistantAudioSettings &audio_settings);
|
||||
void stop_voice_assistant();
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
||||
#endif
|
||||
@ -106,6 +101,11 @@ class APIServer : public Component, public Controller {
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
|
||||
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
||||
Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
|
||||
return this->client_disconnected_trigger_;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
uint16_t port_{6053};
|
||||
@ -115,6 +115,8 @@ class APIServer : public Component, public Controller {
|
||||
std::string password_;
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||
|
@ -48,7 +48,7 @@ void CaptivePortal::start() {
|
||||
this->dns_server_ = make_unique<DNSServer>();
|
||||
this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();
|
||||
this->dns_server_->start(53, "*", IPAddress(ip));
|
||||
this->dns_server_->start(53, "*", ip);
|
||||
#endif
|
||||
|
||||
this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) {
|
||||
|
208
esphome/components/dfrobot_sen0395/__init__.py
Normal file
208
esphome/components/dfrobot_sen0395/__init__.py
Normal file
@ -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
|
89
esphome/components/dfrobot_sen0395/automation.h
Normal file
89
esphome/components/dfrobot_sen0395/automation.h
Normal file
@ -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
|
22
esphome/components/dfrobot_sen0395/binary_sensor.py
Normal file
22
esphome/components/dfrobot_sen0395/binary_sensor.py
Normal file
@ -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))
|
329
esphome/components/dfrobot_sen0395/commands.cpp
Normal file
329
esphome/components/dfrobot_sen0395/commands.cpp
Normal file
@ -0,0 +1,329 @@
|
||||
#include "commands.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "dfrobot_sen0395.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
static const char *const TAG = "dfrobot_sen0395.commands";
|
||||
|
||||
uint8_t Command::execute(DfrobotSen0395Component *parent) {
|
||||
this->parent_ = parent;
|
||||
if (this->cmd_sent_) {
|
||||
if (this->parent_->read_message_()) {
|
||||
std::string message(this->parent_->read_buffer_);
|
||||
if (message.rfind("is not recognized as a CLI command") != std::string::npos) {
|
||||
ESP_LOGD(TAG, "Command not recognized properly by sensor");
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying...");
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
uint8_t rc = on_message(message);
|
||||
if (rc == 2) {
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying...");
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1; // Command done
|
||||
}
|
||||
} else if (rc == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
this->parent_->find_prompt_();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
|
||||
ESP_LOGD(TAG, "Command timeout");
|
||||
if (this->retries_left_ > 0) {
|
||||
this->retries_left_ -= 1;
|
||||
this->cmd_sent_ = false;
|
||||
ESP_LOGD(TAG, "Retrying...");
|
||||
} else {
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
} else if (this->parent_->send_cmd_(this->cmd_.c_str(), this->cmd_duration_ms_)) {
|
||||
this->cmd_sent_ = true;
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t ReadStateCommand::execute(DfrobotSen0395Component *parent) {
|
||||
this->parent_ = parent;
|
||||
if (this->parent_->read_message_()) {
|
||||
std::string message(this->parent_->read_buffer_);
|
||||
if (message.rfind("$JYBSS,0, , , *") != std::string::npos) {
|
||||
this->parent_->set_detected_(false);
|
||||
this->parent_->set_active(true);
|
||||
return 1; // Command done
|
||||
} else if (message.rfind("$JYBSS,1, , , *") != std::string::npos) {
|
||||
this->parent_->set_detected_(true);
|
||||
this->parent_->set_active(true);
|
||||
return 1; // Command done
|
||||
}
|
||||
}
|
||||
if (millis() - this->parent_->ts_last_cmd_sent_ > this->timeout_ms_) {
|
||||
return 1; // Command done, timeout
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
uint8_t ReadStateCommand::on_message(std::string &message) { return 1; }
|
||||
|
||||
uint8_t PowerCommand::on_message(std::string &message) {
|
||||
if (message == "sensor stopped already") {
|
||||
this->parent_->set_active(false);
|
||||
ESP_LOGI(TAG, "Stopped sensor (already stopped)");
|
||||
return 1; // Command done
|
||||
} else if (message == "sensor started already") {
|
||||
this->parent_->set_active(true);
|
||||
ESP_LOGI(TAG, "Started sensor (already started)");
|
||||
return 1; // Command done
|
||||
} else if (message == "new parameter isn't save, can't startSensor") {
|
||||
this->parent_->set_active(false);
|
||||
ESP_LOGE(TAG, "Can't start sensor! (Use SaveCfgCommand to save config first)");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
this->parent_->set_active(this->power_on_);
|
||||
if (this->power_on_) {
|
||||
ESP_LOGI(TAG, "Started sensor");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Stopped sensor");
|
||||
}
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
DetRangeCfgCommand::DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3,
|
||||
float min4, float max4) {
|
||||
// TODO: Print warning when values are rounded
|
||||
if (min1 < 0 || max1 < 0) {
|
||||
this->min1_ = min1 = 0;
|
||||
this->max1_ = max1 = 0;
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
ESP_LOGW(TAG, "DetRangeCfgCommand invalid input parameters. Using range config 0 0.");
|
||||
|
||||
this->cmd_ = "detRangeCfg -1 0 0";
|
||||
} else if (min2 < 0 || max2 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = this->max2_ = max2 = this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 =
|
||||
this->max4_ = max4 = -1;
|
||||
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f", min1 / 0.15, max1 / 0.15);
|
||||
} else if (min3 < 0 || max3 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = this->max3_ = max3 = this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 %.0f %.0f %.0f %.0f", min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15);
|
||||
} else if (min4 < 0 || max4 < 0) {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min4_ = min4 = this->max4_ = max4 = -1;
|
||||
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15);
|
||||
} else {
|
||||
this->min1_ = min1 = round(min1 / 0.15) * 0.15;
|
||||
this->max1_ = max1 = round(max1 / 0.15) * 0.15;
|
||||
this->min2_ = min2 = round(min2 / 0.15) * 0.15;
|
||||
this->max2_ = max2 = round(max2 / 0.15) * 0.15;
|
||||
this->min3_ = min3 = round(min3 / 0.15) * 0.15;
|
||||
this->max3_ = max3 = round(max3 / 0.15) * 0.15;
|
||||
this->min4_ = min4 = round(min4 / 0.15) * 0.15;
|
||||
this->max4_ = max4 = round(max4 / 0.15) * 0.15;
|
||||
|
||||
this->cmd_ = str_sprintf("detRangeCfg -1 "
|
||||
"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f",
|
||||
min1 / 0.15, max1 / 0.15, min2 / 0.15, max2 / 0.15, min3 / 0.15, max3 / 0.15, min4 / 0.15,
|
||||
max4 / 0.15);
|
||||
}
|
||||
|
||||
this->min1_ = min1;
|
||||
this->max1_ = max1;
|
||||
this->min2_ = min2;
|
||||
this->max2_ = max2;
|
||||
this->min3_ = min3;
|
||||
this->max3_ = max3;
|
||||
this->min4_ = min4;
|
||||
this->max4_ = max4;
|
||||
};
|
||||
|
||||
uint8_t DetRangeCfgCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure range config. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated detection area config:");
|
||||
ESP_LOGI(TAG, "Detection area 1 from %.02fm to %.02fm.", this->min1_, this->max1_);
|
||||
if (this->min2_ >= 0 && this->max2_ >= 0) {
|
||||
ESP_LOGI(TAG, "Detection area 2 from %.02fm to %.02fm.", this->min2_, this->max2_);
|
||||
}
|
||||
if (this->min3_ >= 0 && this->max3_ >= 0) {
|
||||
ESP_LOGI(TAG, "Detection area 3 from %.02fm to %.02fm.", this->min3_, this->max3_);
|
||||
}
|
||||
if (this->min4_ >= 0 && this->max4_ >= 0) {
|
||||
ESP_LOGI(TAG, "Detection area 4 from %.02fm to %.02fm.", this->min4_, this->max4_);
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet.
|
||||
}
|
||||
|
||||
OutputLatencyCommand::OutputLatencyCommand(float delay_after_detection, float delay_after_disappear) {
|
||||
delay_after_detection = round(delay_after_detection / 0.025) * 0.025;
|
||||
delay_after_disappear = round(delay_after_disappear / 0.025) * 0.025;
|
||||
if (delay_after_detection < 0)
|
||||
delay_after_detection = 0;
|
||||
if (delay_after_detection > 1638.375)
|
||||
delay_after_detection = 1638.375;
|
||||
if (delay_after_disappear < 0)
|
||||
delay_after_disappear = 0;
|
||||
if (delay_after_disappear > 1638.375)
|
||||
delay_after_disappear = 1638.375;
|
||||
|
||||
this->delay_after_detection_ = delay_after_detection;
|
||||
this->delay_after_disappear_ = delay_after_disappear;
|
||||
|
||||
this->cmd_ = str_sprintf("outputLatency -1 %.0f %.0f", delay_after_detection / 0.025, delay_after_disappear / 0.025);
|
||||
};
|
||||
|
||||
uint8_t OutputLatencyCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure output latency. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated output latency config:");
|
||||
ESP_LOGI(TAG, "Signal that someone was detected is delayed by %.02fs.", this->delay_after_detection_);
|
||||
ESP_LOGI(TAG, "Signal that nobody is detected anymore is delayed by %.02fs.", this->delay_after_disappear_);
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SensorCfgStartCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot configure sensor startup behavior. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Updated sensor startup behavior:");
|
||||
if (startup_mode_) {
|
||||
this->parent_->set_start_after_boot(true);
|
||||
ESP_LOGI(TAG, "Sensor will start automatically after power-on.");
|
||||
} else {
|
||||
this->parent_->set_start_after_boot(false);
|
||||
ESP_LOGI(TAG, "Sensor needs to be started manually after power-on.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t FactoryResetCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot factory reset. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Sensor factory reset done.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t ResetSystemCommand::on_message(std::string &message) {
|
||||
if (message == "leapMMW:/>") {
|
||||
ESP_LOGI(TAG, "Restarted sensor.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SaveCfgCommand::on_message(std::string &message) {
|
||||
if (message == "no parameter has changed") {
|
||||
ESP_LOGI(TAG, "Not saving config (no parameter changed).");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Saved config. Saving a lot may damage the sensor.");
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t LedModeCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set led mode. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set led mode done.");
|
||||
if (this->active_) {
|
||||
this->parent_->set_led_active(true);
|
||||
ESP_LOGI(TAG, "Sensor LED will blink.");
|
||||
} else {
|
||||
this->parent_->set_led_active(false);
|
||||
ESP_LOGI(TAG, "Turned off LED.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t UartOutputCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set uart output mode. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set uart mode done.");
|
||||
if (this->active_) {
|
||||
this->parent_->set_uart_presence_active(true);
|
||||
ESP_LOGI(TAG, "Presence information is sent via UART and GPIO.");
|
||||
} else {
|
||||
this->parent_->set_uart_presence_active(false);
|
||||
ESP_LOGI(TAG, "Presence information is only sent via GPIO.");
|
||||
}
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
uint8_t SensitivityCommand::on_message(std::string &message) {
|
||||
if (message == "sensor is not stopped") {
|
||||
ESP_LOGE(TAG, "Cannot set sensitivity. Sensor is not stopped!");
|
||||
return 1; // Command done
|
||||
} else if (message == "Done") {
|
||||
ESP_LOGI(TAG, "Set sensitivity done. Set to value %d.", this->sensitivity_);
|
||||
ESP_LOGD(TAG, "Used command: %s", this->cmd_.c_str());
|
||||
return 1; // Command done
|
||||
}
|
||||
return 0; // Command not done yet
|
||||
}
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
156
esphome/components/dfrobot_sen0395/commands.h
Normal file
156
esphome/components/dfrobot_sen0395/commands.h
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
class DfrobotSen0395Component;
|
||||
|
||||
// Use command queue and time stamps to avoid blocking.
|
||||
// When component has run time, check if minimum time (1s) between
|
||||
// commands has passed. After that run a command from the queue.
|
||||
class Command {
|
||||
public:
|
||||
virtual ~Command() = default;
|
||||
virtual uint8_t execute(DfrobotSen0395Component *parent);
|
||||
virtual uint8_t on_message(std::string &message) = 0;
|
||||
|
||||
protected:
|
||||
DfrobotSen0395Component *parent_{nullptr};
|
||||
std::string cmd_;
|
||||
bool cmd_sent_{false};
|
||||
int8_t retries_left_{2};
|
||||
uint32_t cmd_duration_ms_{1000};
|
||||
uint32_t timeout_ms_{1500};
|
||||
};
|
||||
|
||||
class ReadStateCommand : public Command {
|
||||
public:
|
||||
uint8_t execute(DfrobotSen0395Component *parent) override;
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint32_t timeout_ms_{500};
|
||||
};
|
||||
|
||||
class PowerCommand : public Command {
|
||||
public:
|
||||
PowerCommand(bool power_on) : power_on_(power_on) {
|
||||
if (power_on) {
|
||||
cmd_ = "sensorStart";
|
||||
} else {
|
||||
cmd_ = "sensorStop";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool power_on_;
|
||||
};
|
||||
|
||||
class DetRangeCfgCommand : public Command {
|
||||
public:
|
||||
DetRangeCfgCommand(float min1, float max1, float min2, float max2, float min3, float max3, float min4, float max4);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
float min1_, max1_, min2_, max2_, min3_, max3_, min4_, max4_;
|
||||
// TODO: Set min max values in component, so they can be published as sensor.
|
||||
};
|
||||
|
||||
class OutputLatencyCommand : public Command {
|
||||
public:
|
||||
OutputLatencyCommand(float delay_after_detection, float delay_after_disappear);
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
float delay_after_detection_;
|
||||
float delay_after_disappear_;
|
||||
};
|
||||
|
||||
class SensorCfgStartCommand : public Command {
|
||||
public:
|
||||
SensorCfgStartCommand(bool startup_mode) : startup_mode_(startup_mode) {
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "sensorCfgStart %d", startup_mode);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool startup_mode_;
|
||||
};
|
||||
|
||||
class FactoryResetCommand : public Command {
|
||||
public:
|
||||
FactoryResetCommand() { cmd_ = "factoryReset 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; };
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class ResetSystemCommand : public Command {
|
||||
public:
|
||||
ResetSystemCommand() { cmd_ = "resetSystem"; }
|
||||
uint8_t on_message(std::string &message) override;
|
||||
};
|
||||
|
||||
class SaveCfgCommand : public Command {
|
||||
public:
|
||||
SaveCfgCommand() { cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; }
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint32_t cmd_duration_ms_{3000};
|
||||
uint32_t timeout_ms_{3500};
|
||||
};
|
||||
|
||||
class LedModeCommand : public Command {
|
||||
public:
|
||||
LedModeCommand(bool active) : active_(active) {
|
||||
if (active) {
|
||||
cmd_ = "setLedMode 1 0";
|
||||
} else {
|
||||
cmd_ = "setLedMode 1 1";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class UartOutputCommand : public Command {
|
||||
public:
|
||||
UartOutputCommand(bool active) : active_(active) {
|
||||
if (active) {
|
||||
cmd_ = "setUartOutput 1 1";
|
||||
} else {
|
||||
cmd_ = "setUartOutput 1 0";
|
||||
}
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
bool active_;
|
||||
};
|
||||
|
||||
class SensitivityCommand : public Command {
|
||||
public:
|
||||
SensitivityCommand(uint8_t sensitivity) : sensitivity_(sensitivity) {
|
||||
if (sensitivity > 9)
|
||||
sensitivity_ = sensitivity = 9;
|
||||
char tmp_cmd[20] = {0};
|
||||
sprintf(tmp_cmd, "setSensitivity %d", sensitivity);
|
||||
cmd_ = std::string(tmp_cmd);
|
||||
};
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint8_t sensitivity_;
|
||||
};
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
142
esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp
Normal file
142
esphome/components/dfrobot_sen0395/dfrobot_sen0395.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
#include "dfrobot_sen0395.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
static const char *const TAG = "dfrobot_sen0395";
|
||||
const char ASCII_CR = 0x0D;
|
||||
const char ASCII_LF = 0x0A;
|
||||
|
||||
void DfrobotSen0395Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Dfrobot Mmwave Radar:");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Registered", this->detected_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
LOG_SWITCH(" ", "Sensor Active Switch", this->sensor_active_switch_);
|
||||
LOG_SWITCH(" ", "Turn on LED Switch", this->turn_on_led_switch_);
|
||||
LOG_SWITCH(" ", "Presence via UART Switch", this->presence_via_uart_switch_);
|
||||
LOG_SWITCH(" ", "Start after Boot Switch", this->start_after_boot_switch_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::loop() {
|
||||
if (cmd_queue_.is_empty()) {
|
||||
// Command queue empty. Read sensor state.
|
||||
cmd_queue_.enqueue(make_unique<ReadStateCommand>());
|
||||
}
|
||||
|
||||
// Commands are non-blocking and need to be called repeatedly.
|
||||
if (cmd_queue_.process(this)) {
|
||||
// Dequeue if command is done
|
||||
cmd_queue_.dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
int8_t DfrobotSen0395Component::enqueue(std::unique_ptr<Command> cmd) {
|
||||
return cmd_queue_.enqueue(std::move(cmd)); // Transfer ownership using std::move
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::read_message_() {
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
this->read_byte(&byte);
|
||||
|
||||
if (this->read_pos_ == MMWAVE_READ_BUFFER_LENGTH)
|
||||
this->read_pos_ = 0;
|
||||
|
||||
ESP_LOGVV(TAG, "Buffer pos: %u %d", this->read_pos_, byte);
|
||||
|
||||
if (byte == ASCII_CR)
|
||||
continue;
|
||||
if (byte >= 0x7F)
|
||||
byte = '?'; // needs to be valid utf8 string for log functions.
|
||||
this->read_buffer_[this->read_pos_] = byte;
|
||||
|
||||
if (this->read_pos_ == 9 && byte == '>')
|
||||
this->read_buffer_[++this->read_pos_] = ASCII_LF;
|
||||
|
||||
if (this->read_buffer_[this->read_pos_] == ASCII_LF) {
|
||||
this->read_buffer_[this->read_pos_] = 0;
|
||||
this->read_pos_ = 0;
|
||||
ESP_LOGV(TAG, "Message: %s", this->read_buffer_);
|
||||
return 1; // Full message in buffer
|
||||
} else {
|
||||
this->read_pos_++;
|
||||
}
|
||||
}
|
||||
return 0; // No full message yet
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::find_prompt_() {
|
||||
if (this->read_message_()) {
|
||||
std::string message(this->read_buffer_);
|
||||
if (message.rfind("leapMMW:/>") != std::string::npos) {
|
||||
return 1; // Prompt found
|
||||
}
|
||||
}
|
||||
return 0; // Not found yet
|
||||
}
|
||||
|
||||
uint8_t DfrobotSen0395Component::send_cmd_(const char *cmd, uint32_t duration) {
|
||||
// The interval between two commands must be larger than the specified duration (in ms).
|
||||
if (millis() - ts_last_cmd_sent_ > duration) {
|
||||
this->write_str(cmd);
|
||||
ts_last_cmd_sent_ = millis();
|
||||
return 1; // Command sent
|
||||
}
|
||||
// Could not send command yet as command duration did not fully pass yet.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DfrobotSen0395Component::set_detected_(bool detected) {
|
||||
this->detected_ = detected;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->detected_binary_sensor_ != nullptr)
|
||||
this->detected_binary_sensor_->publish_state(detected);
|
||||
#endif
|
||||
}
|
||||
|
||||
int8_t CircularCommandQueue::enqueue(std::unique_ptr<Command> cmd) {
|
||||
if (this->is_full()) {
|
||||
ESP_LOGE(TAG, "Command queue is full");
|
||||
return -1;
|
||||
} else if (this->is_empty())
|
||||
front_++;
|
||||
rear_ = (rear_ + 1) % COMMAND_QUEUE_SIZE;
|
||||
commands_[rear_] = std::move(cmd); // Transfer ownership using std::move
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<Command> CircularCommandQueue::dequeue() {
|
||||
if (this->is_empty())
|
||||
return nullptr;
|
||||
std::unique_ptr<Command> dequeued_cmd = std::move(commands_[front_]);
|
||||
if (front_ == rear_) {
|
||||
front_ = -1;
|
||||
rear_ = -1;
|
||||
} else
|
||||
front_ = (front_ + 1) % COMMAND_QUEUE_SIZE;
|
||||
|
||||
return dequeued_cmd;
|
||||
}
|
||||
|
||||
bool CircularCommandQueue::is_empty() { return front_ == -1; }
|
||||
|
||||
bool CircularCommandQueue::is_full() { return (rear_ + 1) % COMMAND_QUEUE_SIZE == front_; }
|
||||
|
||||
// Run execute method of first in line command.
|
||||
// Execute is non-blocking and has to be called until it returns 1.
|
||||
uint8_t CircularCommandQueue::process(DfrobotSen0395Component *parent) {
|
||||
if (!is_empty()) {
|
||||
return commands_[front_]->execute(parent);
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
125
esphome/components/dfrobot_sen0395/dfrobot_sen0395.h
Normal file
125
esphome/components/dfrobot_sen0395/dfrobot_sen0395.h
Normal file
@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#endif
|
||||
|
||||
#include "commands.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
const uint8_t MMWAVE_READ_BUFFER_LENGTH = 255;
|
||||
|
||||
// forward declaration due to circular dependency
|
||||
class DfrobotSen0395Component;
|
||||
|
||||
static const uint8_t COMMAND_QUEUE_SIZE = 20;
|
||||
|
||||
class CircularCommandQueue {
|
||||
public:
|
||||
int8_t enqueue(std::unique_ptr<Command> cmd);
|
||||
std::unique_ptr<Command> dequeue();
|
||||
bool is_empty();
|
||||
bool is_full();
|
||||
uint8_t process(DfrobotSen0395Component *parent);
|
||||
|
||||
protected:
|
||||
int front_{-1};
|
||||
int rear_{-1};
|
||||
std::unique_ptr<Command> commands_[COMMAND_QUEUE_SIZE];
|
||||
};
|
||||
|
||||
class DfrobotSen0395Component : public uart::UARTDevice, public Component {
|
||||
#ifdef USE_SWITCH
|
||||
SUB_SWITCH(sensor_active)
|
||||
SUB_SWITCH(turn_on_led)
|
||||
SUB_SWITCH(presence_via_uart)
|
||||
SUB_SWITCH(start_after_boot)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
void set_active(bool active) {
|
||||
if (active != active_) {
|
||||
#ifdef USE_SWITCH
|
||||
if (this->sensor_active_switch_ != nullptr)
|
||||
this->sensor_active_switch_->publish_state(active);
|
||||
#endif
|
||||
active_ = active;
|
||||
}
|
||||
}
|
||||
bool is_active() { return active_; }
|
||||
|
||||
void set_led_active(bool active) {
|
||||
if (led_active_ != active) {
|
||||
#ifdef USE_SWITCH
|
||||
if (this->turn_on_led_switch_ != nullptr)
|
||||
this->turn_on_led_switch_->publish_state(active);
|
||||
#endif
|
||||
led_active_ = active;
|
||||
}
|
||||
}
|
||||
bool is_led_active() { return led_active_; }
|
||||
|
||||
void set_uart_presence_active(bool active) {
|
||||
uart_presence_active_ = active;
|
||||
#ifdef USE_SWITCH
|
||||
if (this->presence_via_uart_switch_ != nullptr)
|
||||
this->presence_via_uart_switch_->publish_state(active);
|
||||
#endif
|
||||
}
|
||||
bool is_uart_presence_active() { return uart_presence_active_; }
|
||||
|
||||
void set_start_after_boot(bool start) {
|
||||
start_after_boot_ = start;
|
||||
#ifdef USE_SWITCH
|
||||
if (this->start_after_boot_switch_ != nullptr)
|
||||
this->start_after_boot_switch_->publish_state(start);
|
||||
#endif
|
||||
}
|
||||
bool does_start_after_boot() { return start_after_boot_; }
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_detected_binary_sensor(binary_sensor::BinarySensor *detected_binary_sensor) {
|
||||
detected_binary_sensor_ = detected_binary_sensor;
|
||||
}
|
||||
#endif
|
||||
|
||||
int8_t enqueue(std::unique_ptr<Command> cmd);
|
||||
|
||||
protected:
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *detected_binary_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
bool detected_{false};
|
||||
bool active_{false};
|
||||
bool led_active_{false};
|
||||
bool uart_presence_active_{false};
|
||||
bool start_after_boot_{false};
|
||||
char read_buffer_[MMWAVE_READ_BUFFER_LENGTH];
|
||||
size_t read_pos_{0};
|
||||
CircularCommandQueue cmd_queue_;
|
||||
uint32_t ts_last_cmd_sent_{0};
|
||||
|
||||
uint8_t read_message_();
|
||||
uint8_t find_prompt_();
|
||||
uint8_t send_cmd_(const char *cmd, uint32_t duration);
|
||||
|
||||
void set_detected_(bool detected);
|
||||
|
||||
friend class Command;
|
||||
friend class ReadStateCommand;
|
||||
};
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
65
esphome/components/dfrobot_sen0395/switch/__init__.py
Normal file
65
esphome/components/dfrobot_sen0395/switch/__init__.py
Normal file
@ -0,0 +1,65 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG, CONF_TYPE
|
||||
|
||||
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
|
||||
|
||||
|
||||
DEPENDENCIES = ["dfrobot_sen0395"]
|
||||
|
||||
dfrobot_sen0395_ns = cg.esphome_ns.namespace("dfrobot_sen0395")
|
||||
DfrobotSen0395Switch = dfrobot_sen0395_ns.class_(
|
||||
"DfrobotSen0395Switch",
|
||||
switch.Switch,
|
||||
cg.Component,
|
||||
cg.Parented.template(DfrobotSen0395Component),
|
||||
)
|
||||
|
||||
Sen0395PowerSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395PowerSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
Sen0395LedSwitch = dfrobot_sen0395_ns.class_("Sen0395LedSwitch", DfrobotSen0395Switch)
|
||||
Sen0395UartPresenceSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395UartPresenceSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
|
||||
"Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
|
||||
)
|
||||
|
||||
_SWITCH_SCHEMA = (
|
||||
switch.switch_schema(
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
"sensor_active": _SWITCH_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(Sen0395PowerSwitch)}
|
||||
),
|
||||
"turn_on_led": _SWITCH_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(Sen0395LedSwitch)}
|
||||
),
|
||||
"presence_via_uart": _SWITCH_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(Sen0395UartPresenceSwitch)}
|
||||
),
|
||||
"start_after_boot": _SWITCH_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(Sen0395StartAfterBootSwitch)}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_DFROBOT_SEN0395_ID])
|
||||
var = await switch.new_switch(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, parent)
|
||||
cg.add(getattr(parent, f"set_{config[CONF_TYPE]}_switch")(var))
|
@ -0,0 +1,48 @@
|
||||
#include "dfrobot_sen0395_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
void Sen0395PowerSwitch::write_state(bool state) { this->parent_->enqueue(make_unique<PowerCommand>(state)); }
|
||||
|
||||
void Sen0395LedSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<LedModeCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
void Sen0395UartPresenceSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<UartOutputCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
void Sen0395StartAfterBootSwitch::write_state(bool state) {
|
||||
bool was_active = false;
|
||||
if (this->parent_->is_active()) {
|
||||
was_active = true;
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(false));
|
||||
}
|
||||
this->parent_->enqueue(make_unique<SensorCfgStartCommand>(state));
|
||||
this->parent_->enqueue(make_unique<SaveCfgCommand>());
|
||||
if (was_active) {
|
||||
this->parent_->enqueue(make_unique<PowerCommand>(true));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/switch/switch.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "../dfrobot_sen0395.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace dfrobot_sen0395 {
|
||||
|
||||
class DfrobotSen0395Switch : public switch_::Switch, public Component, public Parented<DfrobotSen0395Component> {};
|
||||
|
||||
class Sen0395PowerSwitch : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395LedSwitch : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395UartPresenceSwitch : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
class Sen0395StartAfterBootSwitch : public DfrobotSen0395Switch {
|
||||
public:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace dfrobot_sen0395
|
||||
} // namespace esphome
|
@ -95,7 +95,7 @@ void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) {
|
||||
|
||||
void DutyTimeSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Duty Time:");
|
||||
ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval());
|
||||
ESP_LOGCONFIG(TAG, " Update Interval: %" PRId32 "ms", this->get_update_interval());
|
||||
ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_));
|
||||
LOG_SENSOR(" ", "Duty Time Sensor:", this);
|
||||
LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
@ -194,8 +194,8 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
int64_t frame_time = millis() - last_frame;
|
||||
last_frame = millis();
|
||||
|
||||
ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
|
||||
1000.0 / (uint32_t) frame_time);
|
||||
ESP_LOGD(TAG, "MJPG: %" PRIu32 "B %" PRIu32 "ms (%.1ffps)", (uint32_t) image->get_data_length(),
|
||||
(uint32_t) frame_time, 1000.0 / (uint32_t) frame_time);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
|
||||
|
||||
esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
|
||||
|
||||
ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
|
||||
ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cinttypes>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
|
@ -137,11 +137,10 @@ def validate_weight_name(value):
|
||||
|
||||
|
||||
def download_gfonts(value):
|
||||
wght = value[CONF_WEIGHT]
|
||||
if value[CONF_ITALIC]:
|
||||
wght = f"1,{wght}"
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}"
|
||||
url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}"
|
||||
name = (
|
||||
f"{value[CONF_FAMILY]}:ital,wght@{int(value[CONF_ITALIC])},{value[CONF_WEIGHT]}"
|
||||
)
|
||||
url = f"https://fonts.googleapis.com/css2?family={name}"
|
||||
|
||||
path = _compute_gfonts_local_path(value)
|
||||
if path.is_file():
|
||||
|
39
esphome/components/ld2420/__init__.py
Normal file
39
esphome/components/ld2420/__init__.py
Normal file
@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@descipher"]
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
ld2420_ns = cg.esphome_ns.namespace("ld2420")
|
||||
LD2420Component = ld2420_ns.class_("LD2420Component", cg.Component, uart.UARTDevice)
|
||||
|
||||
CONF_LD2420_ID = "ld2420_id"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2420Component),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"ld2420_uart",
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
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)
|
33
esphome/components/ld2420/binary_sensor/__init__.py
Normal file
33
esphome/components/ld2420/binary_sensor/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID, DEVICE_CLASS_OCCUPANCY
|
||||
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
|
||||
|
||||
LD2420BinarySensor = ld2420_ns.class_(
|
||||
"LD2420BinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
)
|
||||
|
||||
CONF_HAS_TARGET = "has_target"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2420BinarySensor),
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_HAS_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
|
||||
cg.add(var.set_presence_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
@ -0,0 +1,16 @@
|
||||
#include "ld2420_binary_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.binary_sensor";
|
||||
|
||||
void LD2420BinarySensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
|
||||
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
|
||||
}
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ld2420.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_presence_sensor(binary_sensor::BinarySensor *bsensor) { this->presence_bsensor_ = bsensor; };
|
||||
void on_presence(bool presence) override {
|
||||
if (this->presence_bsensor_ != nullptr) {
|
||||
if (this->presence_bsensor_->state != presence)
|
||||
this->presence_bsensor_->publish_state(presence);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *presence_bsensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
69
esphome/components/ld2420/button/__init__.py
Normal file
69
esphome/components/ld2420/button/__init__.py
Normal file
@ -0,0 +1,69 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_RESTART,
|
||||
ICON_RESTART_ALERT,
|
||||
ICON_DATABASE,
|
||||
)
|
||||
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
|
||||
|
||||
LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button)
|
||||
LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button)
|
||||
LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button)
|
||||
LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button)
|
||||
|
||||
CONF_APPLY_CONFIG = "apply_config"
|
||||
CONF_REVERT_CONFIG = "revert_config"
|
||||
CONF_RESTART_MODULE = "restart_module"
|
||||
CONF_FACTORY_RESET = "factory_reset"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Required(CONF_APPLY_CONFIG): button.button_schema(
|
||||
LD2420ApplyConfigButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART_ALERT,
|
||||
),
|
||||
cv.Optional(CONF_REVERT_CONFIG): button.button_schema(
|
||||
LD2420RevertConfigButton,
|
||||
device_class=DEVICE_CLASS_RESTART,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART,
|
||||
),
|
||||
cv.Optional(CONF_RESTART_MODULE): button.button_schema(
|
||||
LD2420RestartModuleButton,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
LD2420FactoryResetButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_DATABASE,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if apply_config := config.get(CONF_APPLY_CONFIG):
|
||||
b = await button.new_button(apply_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_apply_config_button(b))
|
||||
if revert_config := config.get(CONF_REVERT_CONFIG):
|
||||
b = await button.new_button(revert_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_revert_config_button(b))
|
||||
if restart_config := config.get(CONF_RESTART_MODULE):
|
||||
b = await button.new_button(restart_config)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_restart_module_button(b))
|
||||
if factory_reset := config.get(CONF_FACTORY_RESET):
|
||||
b = await button.new_button(factory_reset)
|
||||
await cg.register_parented(b, config[CONF_LD2420_ID])
|
||||
cg.add(ld2420_component.set_factory_reset_button(b))
|
16
esphome/components/ld2420/button/reconfig_buttons.cpp
Normal file
16
esphome/components/ld2420/button/reconfig_buttons.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "reconfig_buttons.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
static const char *const TAG = "LD2420.button";
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); }
|
||||
void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); }
|
||||
void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); }
|
||||
void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); }
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
42
esphome/components/ld2420/button/reconfig_buttons.h
Normal file
42
esphome/components/ld2420/button/reconfig_buttons.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/button/button.h"
|
||||
#include "../ld2420.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420ApplyConfigButton : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420ApplyConfigButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420RevertConfigButton : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420RevertConfigButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420RestartModuleButton : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420RestartModuleButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
class LD2420FactoryResetButton : public button::Button, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420FactoryResetButton() = default;
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
775
esphome/components/ld2420/ld2420.cpp
Normal file
775
esphome/components/ld2420/ld2420.cpp
Normal file
@ -0,0 +1,775 @@
|
||||
#include "ld2420.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
/*
|
||||
Configure commands - little endian
|
||||
|
||||
No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends.
|
||||
|
||||
All send command frames will have:
|
||||
Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD
|
||||
Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data.
|
||||
Command bytes 6 - 7, uint16_t
|
||||
Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes.
|
||||
Receive
|
||||
Error bytes 8-9 uint16_t, 0 = success, all other positive values = error
|
||||
|
||||
Enable config mode:
|
||||
Send:
|
||||
UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01
|
||||
Command = FF 00 - uint16_t 0x00FF
|
||||
Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002
|
||||
Reply:
|
||||
UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01
|
||||
|
||||
Disable config mode:
|
||||
Send:
|
||||
UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01
|
||||
Command = FE 00 - uint16_t 0x00FE
|
||||
Receive:
|
||||
UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01
|
||||
|
||||
Configure system parameters:
|
||||
|
||||
UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms
|
||||
Command = 12 00 - uint16_t 0x0012, Param
|
||||
There are three documented parameters for modes:
|
||||
00 64 = Basic status mode
|
||||
This mode outputs text as presence "ON" or "OFF" and "Range XXXX"
|
||||
where XXXX is a decimal value for distance in cm
|
||||
00 04 = Energy output mode
|
||||
This mode outputs detailed signal energy values for each gate and the target distance.
|
||||
The data format consist of the following.
|
||||
Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
00 00 = debug output mode
|
||||
This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
|
||||
The data format consist of the following.
|
||||
Header HH, Doppler DD, Range RR, Footer FF
|
||||
HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF
|
||||
AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA
|
||||
|
||||
Configure gate sensitivity parameters:
|
||||
UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01
|
||||
Command = 12 00 - uint16_t 0x0007
|
||||
Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
|
||||
Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
|
||||
*/
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "ld2420";
|
||||
|
||||
float LD2420Component::get_setup_priority() const { return setup_priority::BUS; }
|
||||
|
||||
void LD2420Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420:");
|
||||
ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Number:");
|
||||
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
|
||||
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
|
||||
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
|
||||
LOG_NUMBER(TAG, " Gate Select:", this->gate_select_number_);
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
|
||||
LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
|
||||
}
|
||||
LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_);
|
||||
LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_);
|
||||
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
|
||||
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Select:");
|
||||
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
|
||||
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
|
||||
uint8_t checksum = 0;
|
||||
uint8_t *data_bytes = (uint8_t *) data;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
checksum ^= data_bytes[i]; // XOR operation
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
int LD2420Component::get_firmware_int_(const char *version_string) {
|
||||
std::string version_str = version_string;
|
||||
if (version_str[0] == 'v') {
|
||||
version_str = version_str.substr(1);
|
||||
}
|
||||
version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
|
||||
int version_integer = stoi(version_str);
|
||||
return version_integer;
|
||||
}
|
||||
|
||||
void LD2420Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up LD2420...");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->get_min_max_distances_timeout_();
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
#endif
|
||||
this->get_firmware_version_();
|
||||
const char *pfw = this->ld2420_firmware_ver_;
|
||||
std::string fw_str(pfw);
|
||||
|
||||
for (auto &listener : listeners_) {
|
||||
listener->on_fw_version(fw_str);
|
||||
}
|
||||
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
delay_microseconds_safe(125);
|
||||
this->get_gate_threshold_(gate);
|
||||
}
|
||||
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
||||
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
||||
} else {
|
||||
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
}
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
#endif
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::apply_config_action() {
|
||||
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
|
||||
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
|
||||
ESP_LOGCONFIG(TAG, "No configuration change detected");
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420...");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
delay_microseconds_safe(125);
|
||||
this->set_gate_threshold(gate);
|
||||
}
|
||||
memcpy(¤t_config, &new_config, sizeof(new_config));
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
#endif
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
|
||||
this->set_operating_mode(OP_NORMAL_MODE_STRING);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::factory_reset_action() {
|
||||
ESP_LOGCONFIG(TAG, "Setiing factory defaults...");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
|
||||
this->gate_timeout_number_->state = FACTORY_TIMEOUT;
|
||||
this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
|
||||
this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
|
||||
this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
|
||||
delay_microseconds_safe(125);
|
||||
this->set_gate_threshold(gate);
|
||||
}
|
||||
memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false);
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
this->refresh_gate_config_numbers();
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::restart_module_action() {
|
||||
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
|
||||
this->send_module_restart();
|
||||
delay_microseconds_safe(45000);
|
||||
this->set_config_mode(true);
|
||||
this->set_system_mode(system_mode_);
|
||||
this->set_config_mode(false);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
|
||||
}
|
||||
|
||||
void LD2420Component::revert_config_action() {
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
|
||||
}
|
||||
|
||||
void LD2420Component::loop() {
|
||||
// If there is a active send command do not process it here, the send command call will handle it.
|
||||
if (!get_cmd_active_()) {
|
||||
if (!available())
|
||||
return;
|
||||
static uint8_t buffer[2048];
|
||||
static uint8_t rx_data;
|
||||
while (available()) {
|
||||
rx_data = read();
|
||||
this->readline_(rx_data, buffer, sizeof(buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
|
||||
this->radar_data[gate][sample_number] = gate_energy[gate];
|
||||
}
|
||||
this->total_sample_number_counter++;
|
||||
}
|
||||
|
||||
void LD2420Component::auto_calibrate_sensitivity() {
|
||||
// Calculate average and peak values for each gate
|
||||
const float move_factor = gate_move_sensitivity_factor + 1;
|
||||
const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
|
||||
uint32_t sum = 0;
|
||||
uint16_t peak = 0;
|
||||
|
||||
for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
|
||||
// Calculate average
|
||||
sum += this->radar_data[gate][sample_number];
|
||||
|
||||
// Calculate max value
|
||||
if (this->radar_data[gate][sample_number] > peak) {
|
||||
peak = this->radar_data[gate][sample_number];
|
||||
}
|
||||
}
|
||||
|
||||
// Store average and peak values
|
||||
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
|
||||
if (this->gate_peak[gate] < peak)
|
||||
this->gate_peak[gate] = peak;
|
||||
|
||||
uint32_t calculated_value =
|
||||
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
|
||||
this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
|
||||
calculated_value =
|
||||
(static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
|
||||
this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::report_gate_data() {
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
|
||||
// Output results
|
||||
ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
|
||||
}
|
||||
ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
|
||||
}
|
||||
|
||||
void LD2420Component::set_operating_mode(const std::string &state) {
|
||||
// If unsupported firmware ignore mode select
|
||||
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
|
||||
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
||||
this->operating_selector_->publish_state(state);
|
||||
if (current_operating_mode == OP_CALIBRATE_MODE) {
|
||||
this->set_calibration_(true);
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
this->gate_avg[gate] = 0;
|
||||
this->gate_peak[gate] = 0;
|
||||
for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
|
||||
this->radar_data[gate][i] = 0;
|
||||
}
|
||||
this->total_sample_number_counter = 0;
|
||||
}
|
||||
} else {
|
||||
// Set the current data back so we don't have new data that can be applied in error.
|
||||
if (this->get_calibration_())
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
this->set_calibration_(false);
|
||||
}
|
||||
} else {
|
||||
this->current_operating_mode = OP_SIMPLE_MODE;
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
|
||||
static int pos = 0;
|
||||
|
||||
if (rx_data >= 0) {
|
||||
if (pos < len - 1) {
|
||||
buffer[pos++] = rx_data;
|
||||
buffer[pos] = 0;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
if (pos >= 4) {
|
||||
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
|
||||
this->set_cmd_active_(false); // Set command state to inactive after responce.
|
||||
this->handle_ack_data_(buffer, pos);
|
||||
pos = 0;
|
||||
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
|
||||
this->handle_simple_mode_(buffer, pos);
|
||||
pos = 0;
|
||||
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
|
||||
(get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
|
||||
this->handle_energy_mode_(buffer, pos);
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
||||
uint8_t index = 6; // Start at presence byte position
|
||||
uint16_t range;
|
||||
const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
|
||||
this->set_presence_(buffer[index]);
|
||||
index++;
|
||||
memcpy(&range, &buffer[index], sizeof(range));
|
||||
index += sizeof(range);
|
||||
this->set_distance_(range);
|
||||
for (uint8_t i = 0; i < elements; i++) { // NOLINT
|
||||
memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
|
||||
index += sizeof(this->gate_energy_[0]);
|
||||
}
|
||||
|
||||
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
|
||||
this->update_radar_data(gate_energy_, sample_number_counter);
|
||||
this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
|
||||
}
|
||||
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = millis();
|
||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
||||
return;
|
||||
this->last_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_) {
|
||||
listener->on_distance(get_distance_());
|
||||
listener->on_presence(get_presence_());
|
||||
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
|
||||
}
|
||||
|
||||
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
|
||||
this->auto_calibrate_sensitivity();
|
||||
if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
|
||||
this->report_periodic_millis = current_millis;
|
||||
this->report_gate_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
const uint8_t bufsize = 16;
|
||||
uint8_t index{0};
|
||||
uint8_t pos{0};
|
||||
char *endptr{nullptr};
|
||||
char outbuf[bufsize]{0};
|
||||
while (true) {
|
||||
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
||||
set_presence_(false);
|
||||
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
||||
set_presence_(true);
|
||||
}
|
||||
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
|
||||
if (index < bufsize - 1) {
|
||||
outbuf[index++] = inbuf[pos];
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
if (pos < len - 1) {
|
||||
pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
outbuf[index] = '\0';
|
||||
if (index > 1)
|
||||
set_distance_(strtol(outbuf, &endptr, 10));
|
||||
|
||||
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = millis();
|
||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
||||
return;
|
||||
this->last_normal_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_distance(get_distance_());
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_presence(get_presence_());
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
|
||||
this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
|
||||
uint8_t reg_element = 0;
|
||||
uint8_t data_element = 0;
|
||||
uint16_t data_pos = 0;
|
||||
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
|
||||
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
|
||||
return;
|
||||
} else if (this->cmd_reply_.length < 2) {
|
||||
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
|
||||
return;
|
||||
}
|
||||
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
|
||||
const char *result = this->cmd_reply_.error ? "failure" : "success";
|
||||
if (this->cmd_reply_.error > 0) {
|
||||
return;
|
||||
};
|
||||
this->cmd_reply_.ack = true;
|
||||
switch ((uint16_t) this->cmd_reply_.command) {
|
||||
case (CMD_ENABLE_CONF):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
|
||||
break;
|
||||
case (CMD_DISABLE_CONF):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
|
||||
break;
|
||||
case (CMD_READ_REGISTER):
|
||||
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
|
||||
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
|
||||
data_pos = 0x0A;
|
||||
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
|
||||
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
|
||||
index += CMD_REG_DATA_REPLY_SIZE) {
|
||||
memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||
byteswap(this->cmd_reply_.data[reg_element]);
|
||||
reg_element++;
|
||||
}
|
||||
break;
|
||||
case (CMD_WRITE_REGISTER):
|
||||
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
|
||||
break;
|
||||
case (CMD_WRITE_ABD_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
|
||||
break;
|
||||
case (CMD_READ_ABD_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
|
||||
data_pos = CMD_ABD_DATA_REPLY_START;
|
||||
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
|
||||
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
|
||||
index += CMD_ABD_DATA_REPLY_SIZE) {
|
||||
memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
|
||||
sizeof(this->cmd_reply_.data[data_element]));
|
||||
byteswap(this->cmd_reply_.data[data_element]);
|
||||
data_element++;
|
||||
}
|
||||
break;
|
||||
case (CMD_WRITE_SYS_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
|
||||
break;
|
||||
case (CMD_READ_VERSION):
|
||||
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
|
||||
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
uint8_t error = 0;
|
||||
uint8_t ack_buffer[64];
|
||||
uint8_t cmd_buffer[64];
|
||||
uint16_t loop_count;
|
||||
this->cmd_reply_.ack = false;
|
||||
if (frame.command != CMD_RESTART)
|
||||
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
|
||||
uint8_t retry = 3;
|
||||
while (retry) {
|
||||
// TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices
|
||||
// this is ok for now since the module firmware is changing like the weather atm
|
||||
frame.length = 0;
|
||||
loop_count = 1250;
|
||||
uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
|
||||
|
||||
memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
|
||||
frame.length += sizeof(frame.header);
|
||||
|
||||
memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
|
||||
frame.length += sizeof(frame.data_length);
|
||||
|
||||
memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
|
||||
frame.length += sizeof(frame.command);
|
||||
|
||||
for (uint16_t index = 0; index < frame.data_length; index++) {
|
||||
memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
|
||||
frame.length += sizeof(frame.data[index]);
|
||||
}
|
||||
|
||||
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
|
||||
frame.length += sizeof(frame.footer);
|
||||
for (uint16_t index = 0; index < frame.length; index++) {
|
||||
this->write_byte(cmd_buffer[index]);
|
||||
}
|
||||
|
||||
delay_microseconds_safe(500); // give the module a moment to process it
|
||||
error = 0;
|
||||
if (frame.command == CMD_RESTART) {
|
||||
delay_microseconds_safe(25000); // Wait for the restart
|
||||
return 0; // restart does not reply exit now
|
||||
}
|
||||
|
||||
while (!this->cmd_reply_.ack) {
|
||||
while (available()) {
|
||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||
}
|
||||
delay_microseconds_safe(250);
|
||||
if (loop_count <= 0) {
|
||||
error = LD2420_ERROR_TIMEOUT;
|
||||
retry--;
|
||||
break;
|
||||
}
|
||||
loop_count--;
|
||||
}
|
||||
if (this->cmd_reply_.ack)
|
||||
retry = 0;
|
||||
if (this->cmd_reply_.error > 0)
|
||||
handle_cmd_error(error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t LD2420Component::set_config_mode(bool enable) {
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
|
||||
if (enable) {
|
||||
memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
|
||||
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
|
||||
}
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
|
||||
return this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
// Sends a restart and set system running mode to normal
|
||||
void LD2420Component::send_module_restart() { this->ld2420_restart(); }
|
||||
|
||||
void LD2420Component::ld2420_restart() {
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_RESTART;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::get_reg_value_(uint16_t reg) {
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_READ_REGISTER;
|
||||
cmd_frame.data[1] = reg;
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_WRITE_REGISTER;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], ®, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||
cmd_frame.data_length += 2;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
|
||||
|
||||
int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
||||
uint8_t error;
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_READ_ABD_PARAM;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
|
||||
cmd_frame.data_length += 2;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
|
||||
error = this->send_cmd_from_array(cmd_frame);
|
||||
if (error == 0) {
|
||||
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
|
||||
this->current_config.still_thresh[gate] = cmd_reply_.data[1];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
int LD2420Component::get_min_max_distances_timeout_() {
|
||||
uint8_t error;
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_READ_ABD_PARAM;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
|
||||
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
|
||||
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
|
||||
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
|
||||
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
|
||||
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
|
||||
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
|
||||
error = this->send_cmd_from_array(cmd_frame);
|
||||
if (error == 0) {
|
||||
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
|
||||
this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
|
||||
this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
void LD2420Component::set_system_mode(uint16_t mode) {
|
||||
CmdFrameT cmd_frame;
|
||||
uint16_t unknown_parm = 0x0000;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_WRITE_SYS_PARAM;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
|
||||
cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
|
||||
cmd_frame.data_length += sizeof(mode);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
|
||||
cmd_frame.data_length += sizeof(unknown_parm);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
|
||||
if (this->send_cmd_from_array(cmd_frame) == 0)
|
||||
set_mode_(mode);
|
||||
}
|
||||
|
||||
void LD2420Component::get_firmware_version_() {
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_READ_VERSION;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
|
||||
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
|
||||
uint32_t timeout) {
|
||||
// Header H, Length L, Register R, Value V, Footer F
|
||||
// |Min Gate |Max Gate |Timeout |
|
||||
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
|
||||
// FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g.
|
||||
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_WRITE_ABD_PARAM;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
|
||||
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
|
||||
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
|
||||
cmd_frame.data_length += sizeof(min_gate_distance);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
|
||||
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
|
||||
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
|
||||
cmd_frame.data_length += sizeof(max_gate_distance);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
|
||||
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
|
||||
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
|
||||
;
|
||||
cmd_frame.data_length += sizeof(timeout);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
|
||||
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::set_gate_threshold(uint8_t gate) {
|
||||
// Header H, Length L, Command C, Register R, Value V, Footer F
|
||||
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
|
||||
// FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
|
||||
|
||||
uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
|
||||
uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
|
||||
CmdFrameT cmd_frame;
|
||||
cmd_frame.data_length = 0;
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_WRITE_ABD_PARAM;
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
|
||||
cmd_frame.data_length += sizeof(move_threshold_gate);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
|
||||
sizeof(this->new_config.move_thresh[gate]));
|
||||
cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
|
||||
cmd_frame.data_length += sizeof(still_threshold_gate);
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
|
||||
sizeof(this->new_config.still_thresh[gate]));
|
||||
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void LD2420Component::init_gate_config_numbers() {
|
||||
if (this->gate_timeout_number_ != nullptr)
|
||||
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
|
||||
if (this->gate_select_number_ != nullptr)
|
||||
this->gate_select_number_->publish_state(0);
|
||||
if (this->min_gate_distance_number_ != nullptr)
|
||||
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
|
||||
if (this->max_gate_distance_number_ != nullptr)
|
||||
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
|
||||
if (this->gate_move_sensitivity_factor_number_ != nullptr)
|
||||
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
|
||||
if (this->gate_still_sensitivity_factor_number_ != nullptr)
|
||||
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
|
||||
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
|
||||
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
|
||||
this->gate_still_threshold_numbers_[gate]->publish_state(
|
||||
static_cast<uint16_t>(this->current_config.still_thresh[gate]));
|
||||
}
|
||||
if (this->gate_move_threshold_numbers_[gate] != nullptr) {
|
||||
this->gate_move_threshold_numbers_[gate]->publish_state(
|
||||
static_cast<uint16_t>(this->current_config.move_thresh[gate]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::refresh_gate_config_numbers() {
|
||||
this->gate_timeout_number_->publish_state(this->new_config.timeout);
|
||||
this->min_gate_distance_number_->publish_state(this->new_config.min_gate);
|
||||
this->max_gate_distance_number_->publish_state(this->new_config.max_gate);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
272
esphome/components/ld2420/ld2420.h
Normal file
272
esphome/components/ld2420/ld2420.h
Normal file
@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
#include "esphome/components/button/button.h"
|
||||
#endif
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
// Local const's
|
||||
static const uint16_t REFRESH_RATE_MS = 1000;
|
||||
|
||||
// Command sets
|
||||
static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
|
||||
static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
|
||||
static const uint16_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint16_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_MAX_BYTES = 0x64;
|
||||
static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
|
||||
static const uint16_t CMD_PARM_LOW_TRESH = 0x0021;
|
||||
static const uint16_t CMD_PROTOCOL_VER = 0x0002;
|
||||
static const uint16_t CMD_READ_ABD_PARAM = 0x0008;
|
||||
static const uint16_t CMD_READ_REG_ADDR = 0x0020;
|
||||
static const uint16_t CMD_READ_REGISTER = 0x0002;
|
||||
static const uint16_t CMD_READ_SERIAL_NUM = 0x0011;
|
||||
static const uint16_t CMD_READ_SYS_PARAM = 0x0013;
|
||||
static const uint16_t CMD_READ_VERSION = 0x0000;
|
||||
static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
|
||||
static const uint16_t CMD_RESTART = 0x0068;
|
||||
static const uint16_t CMD_SYSTEM_MODE = 0x0000;
|
||||
static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
|
||||
static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
|
||||
static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
|
||||
static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
|
||||
static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
|
||||
static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
|
||||
static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
|
||||
static const uint16_t CMD_WRITE_REGISTER = 0x0001;
|
||||
static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
|
||||
|
||||
static const uint8_t LD2420_ERROR_NONE = 0x00;
|
||||
static const uint8_t LD2420_ERROR_TIMEOUT = 0x02;
|
||||
static const uint8_t LD2420_ERROR_UNKNOWN = 0x01;
|
||||
static const uint8_t LD2420_TOTAL_GATES = 16;
|
||||
static const uint8_t CALIBRATE_SAMPLES = 64;
|
||||
|
||||
// Register address values
|
||||
static const uint16_t CMD_MIN_GATE_REG = 0x0000;
|
||||
static const uint16_t CMD_MAX_GATE_REG = 0x0001;
|
||||
static const uint16_t CMD_TIMEOUT_REG = 0x0004;
|
||||
static const uint16_t CMD_GATE_MOVE_THRESH[LD2420_TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
|
||||
0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
|
||||
0x001C, 0x001D, 0x001E, 0x001F};
|
||||
static const uint16_t CMD_GATE_STILL_THRESH[LD2420_TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
|
||||
0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
|
||||
0x002C, 0x002D, 0x002E, 0x002F};
|
||||
static const uint32_t FACTORY_MOVE_THRESH[LD2420_TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
|
||||
250, 250, 250, 250, 250, 250, 250, 250};
|
||||
static const uint32_t FACTORY_STILL_THRESH[LD2420_TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
|
||||
150, 100, 100, 100, 100, 100, 100, 100};
|
||||
static const uint16_t FACTORY_TIMEOUT = 120;
|
||||
static const uint16_t FACTORY_MIN_GATE = 1;
|
||||
static const uint16_t FACTORY_MAX_GATE = 12;
|
||||
|
||||
// COMMAND_BYTE Header & Footer
|
||||
static const uint8_t CMD_FRAME_COMMAND = 6;
|
||||
static const uint8_t CMD_FRAME_DATA_LENGTH = 4;
|
||||
static const uint32_t CMD_FRAME_FOOTER = 0x01020304;
|
||||
static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
|
||||
static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
|
||||
static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
|
||||
static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
|
||||
static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
|
||||
static const uint8_t CMD_FRAME_STATUS = 7;
|
||||
static const uint8_t CMD_ERROR_WORD = 8;
|
||||
static const uint8_t ENERGY_SENSOR_START = 9;
|
||||
static const uint8_t CALIBRATE_REPORT_INTERVAL = 4;
|
||||
static const int CALIBRATE_VERSION_MIN = 154;
|
||||
static const std::string OP_NORMAL_MODE_STRING = "Normal";
|
||||
static const std::string OP_SIMPLE_MODE_STRING = "Simple";
|
||||
|
||||
enum OpModeStruct : uint8_t { OP_NORMAL_MODE = 1, OP_CALIBRATE_MODE = 2, OP_SIMPLE_MODE = 3 };
|
||||
static const std::map<std::string, uint8_t> OP_MODE_TO_UINT{
|
||||
{"Normal", OP_NORMAL_MODE}, {"Calibrate", OP_CALIBRATE_MODE}, {"Simple", OP_SIMPLE_MODE}};
|
||||
static constexpr const char *ERR_MESSAGE[] = {"None", "Unknown", "Timeout"};
|
||||
|
||||
class LD2420Listener {
|
||||
public:
|
||||
virtual void on_presence(bool presence){};
|
||||
virtual void on_distance(uint16_t distance){};
|
||||
virtual void on_energy(uint16_t *sensor_energy, size_t size){};
|
||||
virtual void on_fw_version(std::string &fw){};
|
||||
};
|
||||
|
||||
class LD2420Component : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
#ifdef USE_SELECT
|
||||
void set_operating_mode_select(select::Select *selector) { this->operating_selector_ = selector; };
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void set_gate_timeout_number(number::Number *number) { this->gate_timeout_number_ = number; };
|
||||
void set_gate_select_number(number::Number *number) { this->gate_select_number_ = number; };
|
||||
void set_min_gate_distance_number(number::Number *number) { this->min_gate_distance_number_ = number; };
|
||||
void set_max_gate_distance_number(number::Number *number) { this->max_gate_distance_number_ = number; };
|
||||
void set_gate_move_sensitivity_factor_number(number::Number *number) {
|
||||
this->gate_move_sensitivity_factor_number_ = number;
|
||||
};
|
||||
void set_gate_still_sensitivity_factor_number(number::Number *number) {
|
||||
this->gate_still_sensitivity_factor_number_ = number;
|
||||
};
|
||||
void set_gate_still_threshold_numbers(int gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; };
|
||||
void set_gate_move_threshold_numbers(int gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; };
|
||||
bool is_gate_select() { return gate_select_number_ != nullptr; };
|
||||
uint8_t get_gate_select_value() { return static_cast<uint8_t>(this->gate_select_number_->state); };
|
||||
float get_min_gate_distance_value() { return min_gate_distance_number_->state; };
|
||||
float get_max_gate_distance_value() { return max_gate_distance_number_->state; };
|
||||
void publish_gate_move_threshold(uint8_t gate) {
|
||||
// With gate_select we only use 1 number pointer, thus we hard code [0]
|
||||
this->gate_move_threshold_numbers_[0]->publish_state(this->new_config.move_thresh[gate]);
|
||||
};
|
||||
void publish_gate_still_threshold(uint8_t gate) {
|
||||
this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]);
|
||||
};
|
||||
void init_gate_config_numbers();
|
||||
void refresh_gate_config_numbers();
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
void set_apply_config_button(button::Button *button) { this->apply_config_button_ = button; };
|
||||
void set_revert_config_button(button::Button *button) { this->revert_config_button_ = button; };
|
||||
void set_restart_module_button(button::Button *button) { this->restart_module_button_ = button; };
|
||||
void set_factory_reset_button(button::Button *button) { this->factory_reset_button_ = button; };
|
||||
#endif
|
||||
void register_listener(LD2420Listener *listener) { this->listeners_.push_back(listener); }
|
||||
|
||||
struct CmdFrameT {
|
||||
uint32_t header{0};
|
||||
uint16_t length{0};
|
||||
uint16_t command{0};
|
||||
uint8_t data[18];
|
||||
uint16_t data_length{0};
|
||||
uint32_t footer{0};
|
||||
};
|
||||
|
||||
struct RegConfigT {
|
||||
uint16_t min_gate{0};
|
||||
uint16_t max_gate{0};
|
||||
uint16_t timeout{0};
|
||||
uint32_t move_thresh[LD2420_TOTAL_GATES];
|
||||
uint32_t still_thresh[LD2420_TOTAL_GATES];
|
||||
};
|
||||
|
||||
void send_module_restart();
|
||||
void restart_module_action();
|
||||
void apply_config_action();
|
||||
void factory_reset_action();
|
||||
void revert_config_action();
|
||||
float get_setup_priority() const override;
|
||||
int send_cmd_from_array(CmdFrameT cmd_frame);
|
||||
void report_gate_data();
|
||||
void handle_cmd_error(uint8_t error);
|
||||
void set_operating_mode(const std::string &state);
|
||||
void auto_calibrate_sensitivity();
|
||||
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
|
||||
uint8_t calc_checksum(void *data, size_t size);
|
||||
|
||||
RegConfigT current_config;
|
||||
RegConfigT new_config;
|
||||
int32_t last_periodic_millis = millis();
|
||||
int32_t report_periodic_millis = millis();
|
||||
int32_t monitor_periodic_millis = millis();
|
||||
int32_t last_normal_periodic_millis = millis();
|
||||
bool output_energy_state{false};
|
||||
uint8_t current_operating_mode{OP_NORMAL_MODE};
|
||||
uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES];
|
||||
uint16_t gate_avg[LD2420_TOTAL_GATES];
|
||||
uint16_t gate_peak[LD2420_TOTAL_GATES];
|
||||
uint8_t sample_number_counter{0};
|
||||
uint16_t total_sample_number_counter{0};
|
||||
float gate_move_sensitivity_factor{0.5};
|
||||
float gate_still_sensitivity_factor{0.5};
|
||||
#ifdef USE_SELECT
|
||||
select::Select *operating_selector_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
button::Button *apply_config_button_{nullptr};
|
||||
button::Button *revert_config_button_{nullptr};
|
||||
button::Button *restart_module_button_{nullptr};
|
||||
button::Button *factory_reset_button_{nullptr};
|
||||
#endif
|
||||
void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout);
|
||||
void set_gate_threshold(uint8_t gate);
|
||||
void set_reg_value(uint16_t reg, uint16_t value);
|
||||
uint8_t set_config_mode(bool enable);
|
||||
void set_system_mode(uint16_t mode);
|
||||
void ld2420_restart();
|
||||
|
||||
protected:
|
||||
struct CmdReplyT {
|
||||
uint8_t command;
|
||||
uint8_t status;
|
||||
uint32_t data[4];
|
||||
uint8_t length;
|
||||
uint16_t error;
|
||||
volatile bool ack;
|
||||
};
|
||||
|
||||
int get_firmware_int_(const char *version_string);
|
||||
void get_firmware_version_();
|
||||
int get_gate_threshold_(uint8_t gate);
|
||||
void get_reg_value_(uint16_t reg);
|
||||
int get_min_max_distances_timeout_();
|
||||
uint16_t get_mode_() { return this->system_mode_; };
|
||||
void set_mode_(uint16_t mode) { this->system_mode_ = mode; };
|
||||
bool get_presence_() { return this->presence_; };
|
||||
void set_presence_(bool presence) { this->presence_ = presence; };
|
||||
uint16_t get_distance_() { return this->distance_; };
|
||||
void set_distance_(uint16_t distance) { this->distance_ = distance; };
|
||||
bool get_cmd_active_() { return this->cmd_active_; };
|
||||
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
|
||||
void handle_simple_mode_(const uint8_t *inbuf, int len);
|
||||
void handle_energy_mode_(uint8_t *buffer, int len);
|
||||
void handle_ack_data_(uint8_t *buffer, int len);
|
||||
void readline_(int rx_data, uint8_t *buffer, int len);
|
||||
void set_calibration_(bool state) { this->calibration_ = state; };
|
||||
bool get_calibration_() { return this->calibration_; };
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
number::Number *gate_timeout_number_{nullptr};
|
||||
number::Number *gate_select_number_{nullptr};
|
||||
number::Number *min_gate_distance_number_{nullptr};
|
||||
number::Number *max_gate_distance_number_{nullptr};
|
||||
number::Number *gate_move_sensitivity_factor_number_{nullptr};
|
||||
number::Number *gate_still_sensitivity_factor_number_{nullptr};
|
||||
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(16);
|
||||
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
|
||||
#endif
|
||||
|
||||
uint16_t gate_energy_[LD2420_TOTAL_GATES];
|
||||
CmdReplyT cmd_reply_;
|
||||
uint32_t timeout_;
|
||||
uint32_t max_distance_gate_;
|
||||
uint32_t min_distance_gate_;
|
||||
uint16_t system_mode_{CMD_SYSTEM_MODE_ENERGY};
|
||||
bool cmd_active_{false};
|
||||
char ld2420_firmware_ver_[8];
|
||||
bool presence_{false};
|
||||
bool calibration_{false};
|
||||
uint16_t distance_{0};
|
||||
uint8_t config_checksum_{0};
|
||||
std::vector<LD2420Listener *> listeners_{};
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
183
esphome/components/ld2420/number/__init__.py
Normal file
183
esphome/components/ld2420/number/__init__.py
Normal file
@ -0,0 +1,183 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
UNIT_SECOND,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_MOTION_SENSOR,
|
||||
ICON_TIMELAPSE,
|
||||
ICON_SCALE,
|
||||
)
|
||||
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
|
||||
|
||||
LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number)
|
||||
LD2420MoveSensFactorNumber = ld2420_ns.class_(
|
||||
"LD2420MoveSensFactorNumber", number.Number
|
||||
)
|
||||
LD2420StillSensFactorNumber = ld2420_ns.class_(
|
||||
"LD2420StillSensFactorNumber", number.Number
|
||||
)
|
||||
LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number)
|
||||
LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number)
|
||||
LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number)
|
||||
LD2420MoveThresholdNumbers = ld2420_ns.class_(
|
||||
"LD2420MoveThresholdNumbers", number.Number
|
||||
)
|
||||
LD2420StillThresholdNumbers = ld2420_ns.class_(
|
||||
"LD2420StillThresholdNumbers", number.Number
|
||||
)
|
||||
CONF_MIN_GATE_DISTANCE = "min_gate_distance"
|
||||
CONF_MAX_GATE_DISTANCE = "max_gate_distance"
|
||||
CONF_STILL_THRESHOLD = "still_threshold"
|
||||
CONF_MOVE_THRESHOLD = "move_threshold"
|
||||
CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity"
|
||||
CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity"
|
||||
CONF_GATE_SELECT = "gate_select"
|
||||
CONF_PRESENCE_TIMEOUT = "presence_timeout"
|
||||
GATE_GROUP = "gate_group"
|
||||
TIMEOUT_GROUP = "timeout_group"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
|
||||
LD2420TimeoutNumber,
|
||||
unit_of_measurement=UNIT_SECOND,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_TIMELAPSE,
|
||||
),
|
||||
cv.Inclusive(CONF_MIN_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
|
||||
LD2420MinDistanceNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Inclusive(CONF_MAX_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
|
||||
LD2420MaxDistanceNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Inclusive(CONF_GATE_SELECT, GATE_GROUP): number.number_schema(
|
||||
LD2420GateSelectNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema(
|
||||
LD2420StillThresholdNumbers,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema(
|
||||
LD2420MoveThresholdNumbers,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema(
|
||||
LD2420MoveSensFactorNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_SCALE,
|
||||
),
|
||||
cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema(
|
||||
LD2420StillSensFactorNumber,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_SCALE,
|
||||
),
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(f"gate_{x}"): (
|
||||
{
|
||||
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
||||
LD2420MoveThresholdNumbers,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
||||
LD2420StillThresholdNumbers,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_MOTION_SENSOR,
|
||||
),
|
||||
}
|
||||
)
|
||||
for x in range(16)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
|
||||
n = await number.new_number(
|
||||
gate_timeout_config, min_value=0, max_value=255, step=5
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_timeout_number(n))
|
||||
if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE):
|
||||
n = await number.new_number(
|
||||
min_distance_gate_config, min_value=0, max_value=15, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_min_gate_distance_number(n))
|
||||
if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE):
|
||||
n = await number.new_number(
|
||||
max_distance_gate_config, min_value=1, max_value=15, step=1
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_max_gate_distance_number(n))
|
||||
if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY):
|
||||
n = await number.new_number(
|
||||
gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n))
|
||||
if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY):
|
||||
n = await number.new_number(
|
||||
gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n))
|
||||
if config.get(CONF_GATE_SELECT):
|
||||
if gate_number := config.get(CONF_GATE_SELECT):
|
||||
n = await number.new_number(gate_number, min_value=0, max_value=15, step=1)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_select_number(n))
|
||||
if gate_still_threshold := config.get(CONF_STILL_THRESHOLD):
|
||||
n = cg.new_Pvariable(gate_still_threshold[CONF_ID])
|
||||
await number.register_number(
|
||||
n, gate_still_threshold, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n))
|
||||
if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD):
|
||||
n = cg.new_Pvariable(gate_move_threshold[CONF_ID])
|
||||
await number.register_number(
|
||||
n, gate_move_threshold, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n))
|
||||
else:
|
||||
for x in range(16):
|
||||
if gate_conf := config.get(f"gate_{x}"):
|
||||
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||
n = cg.new_Pvariable(move_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, move_config, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n))
|
||||
|
||||
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||
n = cg.new_Pvariable(still_config[CONF_ID], x)
|
||||
await number.register_number(
|
||||
n, still_config, min_value=0, max_value=65535, step=25
|
||||
)
|
||||
await cg.register_parented(n, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n))
|
73
esphome/components/ld2420/number/gate_config_number.cpp
Normal file
73
esphome/components/ld2420/number/gate_config_number.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "gate_config_number.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
static const char *const TAG = "LD2420.number";
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
void LD2420TimeoutNumber::control(float timeout) {
|
||||
this->publish_state(timeout);
|
||||
this->parent_->new_config.timeout = timeout;
|
||||
}
|
||||
|
||||
void LD2420MinDistanceNumber::control(float min_gate) {
|
||||
if ((uint16_t) min_gate > this->parent_->new_config.max_gate) {
|
||||
min_gate = this->parent_->get_min_gate_distance_value();
|
||||
} else {
|
||||
this->parent_->new_config.min_gate = (uint16_t) min_gate;
|
||||
}
|
||||
this->publish_state(min_gate);
|
||||
}
|
||||
|
||||
void LD2420MaxDistanceNumber::control(float max_gate) {
|
||||
if ((uint16_t) max_gate < this->parent_->new_config.min_gate) {
|
||||
max_gate = this->parent_->get_max_gate_distance_value();
|
||||
} else {
|
||||
this->parent_->new_config.max_gate = (uint16_t) max_gate;
|
||||
}
|
||||
this->publish_state(max_gate);
|
||||
}
|
||||
|
||||
void LD2420GateSelectNumber::control(float gate_select) {
|
||||
const uint8_t gate = (uint8_t) gate_select;
|
||||
this->publish_state(gate_select);
|
||||
this->parent_->publish_gate_move_threshold(gate);
|
||||
this->parent_->publish_gate_still_threshold(gate);
|
||||
}
|
||||
|
||||
void LD2420MoveSensFactorNumber::control(float move_factor) {
|
||||
this->publish_state(move_factor);
|
||||
this->parent_->gate_move_sensitivity_factor = move_factor;
|
||||
}
|
||||
|
||||
void LD2420StillSensFactorNumber::control(float still_factor) {
|
||||
this->publish_state(still_factor);
|
||||
this->parent_->gate_still_sensitivity_factor = still_factor;
|
||||
}
|
||||
|
||||
LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {}
|
||||
|
||||
void LD2420MoveThresholdNumbers::control(float move_threshold) {
|
||||
this->publish_state(move_threshold);
|
||||
if (!this->parent_->is_gate_select()) {
|
||||
this->parent_->new_config.move_thresh[this->gate_] = move_threshold;
|
||||
} else {
|
||||
this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {}
|
||||
|
||||
void LD2420StillThresholdNumbers::control(float still_threshold) {
|
||||
this->publish_state(still_threshold);
|
||||
if (!this->parent_->is_gate_select()) {
|
||||
this->parent_->new_config.still_thresh[this->gate_] = still_threshold;
|
||||
} else {
|
||||
this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
78
esphome/components/ld2420/number/gate_config_number.h
Normal file
78
esphome/components/ld2420/number/gate_config_number.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "../ld2420.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420TimeoutNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420TimeoutNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float timeout) override;
|
||||
};
|
||||
|
||||
class LD2420MinDistanceNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420MinDistanceNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float min_gate) override;
|
||||
};
|
||||
|
||||
class LD2420MaxDistanceNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420MaxDistanceNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float max_gate) override;
|
||||
};
|
||||
|
||||
class LD2420GateSelectNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420GateSelectNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float gate_select) override;
|
||||
};
|
||||
|
||||
class LD2420MoveSensFactorNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420MoveSensFactorNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float move_factor) override;
|
||||
};
|
||||
|
||||
class LD2420StillSensFactorNumber : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420StillSensFactorNumber() = default;
|
||||
|
||||
protected:
|
||||
void control(float still_factor) override;
|
||||
};
|
||||
|
||||
class LD2420StillThresholdNumbers : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420StillThresholdNumbers() = default;
|
||||
LD2420StillThresholdNumbers(uint8_t gate);
|
||||
|
||||
protected:
|
||||
uint8_t gate_;
|
||||
void control(float still_threshold) override;
|
||||
};
|
||||
|
||||
class LD2420MoveThresholdNumbers : public number::Number, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420MoveThresholdNumbers() = default;
|
||||
LD2420MoveThresholdNumbers(uint8_t gate);
|
||||
|
||||
protected:
|
||||
uint8_t gate_;
|
||||
void control(float move_threshold) override;
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
33
esphome/components/ld2420/select/__init__.py
Normal file
33
esphome/components/ld2420/select/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG
|
||||
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
|
||||
|
||||
CONF_OPERATING_MODE = "operating_mode"
|
||||
CONF_SELECTS = [
|
||||
"Normal",
|
||||
"Calibrate",
|
||||
"Simple",
|
||||
]
|
||||
|
||||
LD2420Select = ld2420_ns.class_("LD2420Select", cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Required(CONF_OPERATING_MODE): select.select_schema(
|
||||
LD2420Select,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
if operating_mode_config := config.get(CONF_OPERATING_MODE):
|
||||
sel = await select.new_select(
|
||||
operating_mode_config,
|
||||
options=[CONF_SELECTS],
|
||||
)
|
||||
await cg.register_parented(sel, config[CONF_LD2420_ID])
|
||||
cg.add(LD2420_component.set_operating_mode_select(sel))
|
16
esphome/components/ld2420/select/operating_mode_select.cpp
Normal file
16
esphome/components/ld2420/select/operating_mode_select.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "operating_mode_select.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.select";
|
||||
|
||||
void LD2420Select::control(const std::string &value) {
|
||||
this->publish_state(value);
|
||||
this->parent_->set_operating_mode(value);
|
||||
}
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
18
esphome/components/ld2420/select/operating_mode_select.h
Normal file
18
esphome/components/ld2420/select/operating_mode_select.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ld2420.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420Select : public Component, public select::Select, public Parented<LD2420Component> {
|
||||
public:
|
||||
LD2420Select() = default;
|
||||
|
||||
protected:
|
||||
void control(const std::string &value) override;
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
35
esphome/components/ld2420/sensor/__init__.py
Normal file
35
esphome/components/ld2420/sensor/__init__.py
Normal file
@ -0,0 +1,35 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER
|
||||
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
|
||||
|
||||
LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONF_MOVING_DISTANCE = "moving_distance"
|
||||
CONF_GATE_ENERGY = "gate_energy"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2420Sensor),
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_MOVING_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
|
||||
cg.add(var.set_distance_sensor(sens))
|
||||
if CONF_GATE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_GATE_ENERGY])
|
||||
cg.add(var.set_energy_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
16
esphome/components/ld2420/sensor/ld2420_sensor.cpp
Normal file
16
esphome/components/ld2420/sensor/ld2420_sensor.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "ld2420_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.sensor";
|
||||
|
||||
void LD2420Sensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
|
||||
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
|
||||
}
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
34
esphome/components/ld2420/sensor/ld2420_sensor.h
Normal file
34
esphome/components/ld2420/sensor/ld2420_sensor.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ld2420.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_distance_sensor(sensor::Sensor *sensor) { this->distance_sensor_ = sensor; }
|
||||
void on_distance(uint16_t distance) override {
|
||||
if (this->distance_sensor_ != nullptr) {
|
||||
if (this->distance_sensor_->get_state() != distance) {
|
||||
this->distance_sensor_->publish_state(distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_energy(uint16_t *gate_energy, size_t size) override {
|
||||
for (size_t active = 0; active < size; active++) {
|
||||
if (this->energy_sensors_[active] != nullptr) {
|
||||
this->energy_sensors_[active]->publish_state(gate_energy[active]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
sensor::Sensor *distance_sensor_{nullptr};
|
||||
std::vector<sensor::Sensor *> energy_sensors_ = std::vector<sensor::Sensor *>(LD2420_TOTAL_GATES);
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
38
esphome/components/ld2420/text_sensor/__init__.py
Normal file
38
esphome/components/ld2420/text_sensor/__init__.py
Normal file
@ -0,0 +1,38 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_CHIP,
|
||||
)
|
||||
|
||||
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
|
||||
|
||||
LD2420TextSensor = ld2420_ns.class_(
|
||||
"LD2420TextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONF_FW_VERSION = "fw_version"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2420TextSensor),
|
||||
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
|
||||
cv.Optional(CONF_FW_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
if CONF_FW_VERSION in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION])
|
||||
cg.add(var.set_fw_version_text_sensor(sens))
|
||||
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
|
||||
cg.add(ld2420.register_listener(var))
|
16
esphome/components/ld2420/text_sensor/text_sensor.cpp
Normal file
16
esphome/components/ld2420/text_sensor/text_sensor.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "text_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
static const char *const TAG = "LD2420.text_sensor";
|
||||
|
||||
void LD2420TextSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
|
||||
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
|
||||
}
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
24
esphome/components/ld2420/text_sensor/text_sensor.h
Normal file
24
esphome/components/ld2420/text_sensor/text_sensor.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ld2420.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2420 {
|
||||
|
||||
class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_fw_version_text_sensor(text_sensor::TextSensor *tsensor) { this->fw_version_text_sensor_ = tsensor; };
|
||||
void on_fw_version(std::string &fw) override {
|
||||
if (this->fw_version_text_sensor_ != nullptr) {
|
||||
this->fw_version_text_sensor_->publish_state(fw);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
text_sensor::TextSensor *fw_version_text_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ld2420
|
||||
} // namespace esphome
|
@ -10,6 +10,7 @@ CONF_STORE_IN_EEPROM = "store_in_eeprom"
|
||||
|
||||
mcp4728_ns = cg.esphome_ns.namespace("mcp4728")
|
||||
MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice)
|
||||
CONF_MCP4728_ID = "mcp4728_id"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
|
@ -1,4 +1,4 @@
|
||||
#include "mcp4728_output.h"
|
||||
#include "mcp4728.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@ -110,12 +110,5 @@ void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain)
|
||||
this->update_ = true;
|
||||
}
|
||||
|
||||
void MCP4728Channel::write_state(float state) {
|
||||
const uint16_t max_duty = 4095;
|
||||
const float duty_rounded = roundf(state * max_duty);
|
||||
auto duty = static_cast<uint16_t>(duty_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, duty);
|
||||
}
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
@ -64,28 +63,5 @@ class MCP4728Component : public Component, public i2c::I2CDevice {
|
||||
bool update_ = false;
|
||||
};
|
||||
|
||||
class MCP4728Channel : public output::FloatOutput {
|
||||
public:
|
||||
MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain,
|
||||
MCP4728PwrDown pwrdown)
|
||||
: parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) {
|
||||
// update VREF
|
||||
parent->select_vref_(channel, vref_);
|
||||
// update PD
|
||||
parent->select_power_down_(channel, pwrdown_);
|
||||
// update GAIN
|
||||
parent->select_gain_(channel, gain_);
|
||||
}
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
MCP4728Component *parent_;
|
||||
MCP4728ChannelIdx channel_;
|
||||
MCP4728Vref vref_;
|
||||
MCP4728Gain gain_;
|
||||
MCP4728PwrDown pwrdown_;
|
||||
};
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
@ -2,12 +2,11 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN
|
||||
from . import MCP4728Component, mcp4728_ns
|
||||
from .. import MCP4728Component, CONF_MCP4728_ID, mcp4728_ns
|
||||
|
||||
DEPENDENCIES = ["mcp4728"]
|
||||
|
||||
MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput)
|
||||
CONF_MCP4728_ID = "mcp4728_id"
|
||||
CONF_VREF = "vref"
|
||||
CONF_POWER_DOWN = "power_down"
|
||||
|
17
esphome/components/mcp4728/output/mcp4728_output.cpp
Normal file
17
esphome/components/mcp4728/output/mcp4728_output.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "mcp4728_output.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp4728 {
|
||||
|
||||
void MCP4728Channel::write_state(float state) {
|
||||
const uint16_t max_duty = 4095;
|
||||
const float duty_rounded = roundf(state * max_duty);
|
||||
auto duty = static_cast<uint16_t>(duty_rounded);
|
||||
this->parent_->set_channel_value_(this->channel_, duty);
|
||||
}
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
32
esphome/components/mcp4728/output/mcp4728_output.h
Normal file
32
esphome/components/mcp4728/output/mcp4728_output.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "../mcp4728.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mcp4728 {
|
||||
|
||||
class MCP4728Channel : public output::FloatOutput {
|
||||
public:
|
||||
MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain,
|
||||
MCP4728PwrDown pwrdown)
|
||||
: parent_(parent), channel_(channel) {
|
||||
// update VREF
|
||||
parent->select_vref_(channel, vref);
|
||||
// update PD
|
||||
parent->select_power_down_(channel, pwrdown);
|
||||
// update GAIN
|
||||
parent->select_gain_(channel, gain);
|
||||
}
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
|
||||
MCP4728Component *parent_;
|
||||
MCP4728ChannelIdx channel_;
|
||||
};
|
||||
|
||||
} // namespace mcp4728
|
||||
} // namespace esphome
|
@ -88,7 +88,7 @@ async def to_code(config):
|
||||
add_idf_component(
|
||||
name="mdns",
|
||||
repo="https://github.com/espressif/esp-protocols.git",
|
||||
ref="mdns-v1.2.0",
|
||||
ref="mdns-v1.2.2",
|
||||
path="components/mdns",
|
||||
)
|
||||
|
||||
|
69
esphome/components/micronova/__init__.py
Normal file
69
esphome/components/micronova/__init__.py
Normal file
@ -0,0 +1,69 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import uart
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jorre05"]
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
CONF_MICRONOVA_ID = "micronova_id"
|
||||
CONF_ENABLE_RX_PIN = "enable_rx_pin"
|
||||
CONF_MEMORY_LOCATION = "memory_location"
|
||||
CONF_MEMORY_ADDRESS = "memory_address"
|
||||
|
||||
micronova_ns = cg.esphome_ns.namespace("micronova")
|
||||
|
||||
MicroNovaFunctions = micronova_ns.enum("MicroNovaFunctions", is_class=True)
|
||||
MICRONOVA_FUNCTIONS_ENUM = {
|
||||
"STOVE_FUNCTION_SWITCH": MicroNovaFunctions.STOVE_FUNCTION_SWITCH,
|
||||
"STOVE_FUNCTION_ROOM_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE,
|
||||
"STOVE_FUNCTION_THERMOSTAT_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE,
|
||||
"STOVE_FUNCTION_FUMES_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE,
|
||||
"STOVE_FUNCTION_STOVE_POWER": MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER,
|
||||
"STOVE_FUNCTION_FAN_SPEED": MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED,
|
||||
"STOVE_FUNCTION_STOVE_STATE": MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE,
|
||||
"STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR": MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR,
|
||||
"STOVE_FUNCTION_WATER_TEMPERATURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE,
|
||||
"STOVE_FUNCTION_WATER_PRESSURE": MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE,
|
||||
"STOVE_FUNCTION_POWER_LEVEL": MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL,
|
||||
"STOVE_FUNCTION_CUSTOM": MicroNovaFunctions.STOVE_FUNCTION_CUSTOM,
|
||||
}
|
||||
|
||||
MicroNova = micronova_ns.class_("MicroNova", cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MicroNova),
|
||||
cv.Required(CONF_ENABLE_RX_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
def MICRONOVA_LISTENER_SCHEMA(default_memory_location, default_memory_address):
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(
|
||||
CONF_MEMORY_LOCATION, default=default_memory_location
|
||||
): cv.hex_int_range(),
|
||||
cv.Optional(
|
||||
CONF_MEMORY_ADDRESS, default=default_memory_address
|
||||
): cv.hex_int_range(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
enable_rx_pin = await cg.gpio_pin_expression(config[CONF_ENABLE_RX_PIN])
|
||||
cg.add(var.set_enable_rx_pin(enable_rx_pin))
|
44
esphome/components/micronova/button/__init__.py
Normal file
44
esphome/components/micronova/button/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import button
|
||||
|
||||
from .. import (
|
||||
MicroNova,
|
||||
MicroNovaFunctions,
|
||||
CONF_MICRONOVA_ID,
|
||||
CONF_MEMORY_LOCATION,
|
||||
CONF_MEMORY_ADDRESS,
|
||||
MICRONOVA_LISTENER_SCHEMA,
|
||||
micronova_ns,
|
||||
)
|
||||
|
||||
MicroNovaButton = micronova_ns.class_("MicroNovaButton", button.Button, cg.Component)
|
||||
|
||||
CONF_CUSTOM_BUTTON = "custom_button"
|
||||
CONF_MEMORY_DATA = "memory_data"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(CONF_CUSTOM_BUTTON): button.button_schema(
|
||||
MicroNovaButton,
|
||||
)
|
||||
.extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0xA0, default_memory_address=0x7D
|
||||
)
|
||||
)
|
||||
.extend({cv.Required(CONF_MEMORY_DATA): cv.hex_int_range()}),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
|
||||
|
||||
if custom_button_config := config.get(CONF_CUSTOM_BUTTON):
|
||||
bt = await button.new_button(custom_button_config, mv)
|
||||
cg.add(bt.set_memory_location(custom_button_config.get(CONF_MEMORY_LOCATION)))
|
||||
cg.add(bt.set_memory_address(custom_button_config.get(CONF_MEMORY_ADDRESS)))
|
||||
cg.add(bt.set_memory_data(custom_button_config[CONF_MEMORY_DATA]))
|
||||
cg.add(bt.set_function(MicroNovaFunctions.STOVE_FUNCTION_CUSTOM))
|
18
esphome/components/micronova/button/micronova_button.cpp
Normal file
18
esphome/components/micronova/button/micronova_button.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
#include "micronova_button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNovaButton::press_action() {
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_CUSTOM:
|
||||
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->micronova_->update();
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
23
esphome/components/micronova/button/micronova_button.h
Normal file
23
esphome/components/micronova/button/micronova_button.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/micronova/micronova.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
class MicroNovaButton : public Component, public button::Button, public MicroNovaButtonListener {
|
||||
public:
|
||||
MicroNovaButton(MicroNova *m) : MicroNovaButtonListener(m) {}
|
||||
void dump_config() override { LOG_BUTTON("", "Micronova button", this); }
|
||||
|
||||
void set_memory_data(uint8_t f) { this->memory_data_ = f; }
|
||||
uint8_t get_memory_data() { return this->memory_data_; }
|
||||
|
||||
protected:
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
148
esphome/components/micronova/micronova.cpp
Normal file
148
esphome/components/micronova/micronova.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include "micronova.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNova::setup() {
|
||||
if (this->enable_rx_pin_ != nullptr) {
|
||||
this->enable_rx_pin_->setup();
|
||||
this->enable_rx_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->enable_rx_pin_->digital_write(false);
|
||||
}
|
||||
this->current_transmission_.request_transmission_time = millis();
|
||||
this->current_transmission_.memory_location = 0;
|
||||
this->current_transmission_.memory_address = 0;
|
||||
this->current_transmission_.reply_pending = false;
|
||||
this->current_transmission_.initiating_listener = nullptr;
|
||||
}
|
||||
|
||||
void MicroNova::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MicroNova:");
|
||||
if (this->enable_rx_pin_ != nullptr) {
|
||||
LOG_PIN(" Enable RX Pin: ", this->enable_rx_pin_);
|
||||
}
|
||||
|
||||
for (auto &mv_sensor : this->micronova_listeners_) {
|
||||
mv_sensor->dump_config();
|
||||
ESP_LOGCONFIG(TAG, " sensor location:%02X, address:%02X", mv_sensor->get_memory_location(),
|
||||
mv_sensor->get_memory_address());
|
||||
}
|
||||
}
|
||||
|
||||
void MicroNova::update() {
|
||||
ESP_LOGD(TAG, "Schedule sensor update");
|
||||
for (auto &mv_listener : this->micronova_listeners_) {
|
||||
mv_listener->set_needs_update(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MicroNova::loop() {
|
||||
// Only read one sensor that needs update per loop
|
||||
// If STOVE_REPLY_DELAY time has passed since last loop()
|
||||
// check for a reply from the stove
|
||||
if ((this->current_transmission_.reply_pending) &&
|
||||
(millis() - this->current_transmission_.request_transmission_time > STOVE_REPLY_DELAY)) {
|
||||
int stove_reply_value = this->read_stove_reply();
|
||||
if (this->current_transmission_.initiating_listener != nullptr) {
|
||||
this->current_transmission_.initiating_listener->process_value_from_stove(stove_reply_value);
|
||||
this->current_transmission_.initiating_listener = nullptr;
|
||||
}
|
||||
this->current_transmission_.reply_pending = false;
|
||||
return;
|
||||
} else if (!this->current_transmission_.reply_pending) {
|
||||
for (auto &mv_listener : this->micronova_listeners_) {
|
||||
if (mv_listener->get_needs_update()) {
|
||||
mv_listener->set_needs_update(false);
|
||||
this->current_transmission_.initiating_listener = mv_listener;
|
||||
mv_listener->request_value_from_stove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MicroNova::request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener) {
|
||||
uint8_t write_data[2] = {0, 0};
|
||||
uint8_t trash_rx;
|
||||
|
||||
if (this->reply_pending_mutex_.try_lock()) {
|
||||
// clear rx buffer.
|
||||
// Stove hickups may cause late replies in the rx
|
||||
while (this->available()) {
|
||||
this->read_byte(&trash_rx);
|
||||
ESP_LOGW(TAG, "Reading excess byte 0x%02X", trash_rx);
|
||||
}
|
||||
|
||||
write_data[0] = location;
|
||||
write_data[1] = address;
|
||||
ESP_LOGV(TAG, "Request from stove [%02X,%02X]", write_data[0], write_data[1]);
|
||||
|
||||
this->enable_rx_pin_->digital_write(true);
|
||||
this->write_array(write_data, 2);
|
||||
this->flush();
|
||||
this->enable_rx_pin_->digital_write(false);
|
||||
|
||||
this->current_transmission_.request_transmission_time = millis();
|
||||
this->current_transmission_.memory_location = location;
|
||||
this->current_transmission_.memory_address = address;
|
||||
this->current_transmission_.reply_pending = true;
|
||||
this->current_transmission_.initiating_listener = listener;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Reply is pending, skipping read request");
|
||||
}
|
||||
}
|
||||
|
||||
int MicroNova::read_stove_reply() {
|
||||
uint8_t reply_data[2] = {0, 0};
|
||||
uint8_t checksum = 0;
|
||||
|
||||
// assert enable_rx_pin is false
|
||||
this->read_array(reply_data, 2);
|
||||
|
||||
this->reply_pending_mutex_.unlock();
|
||||
ESP_LOGV(TAG, "Reply from stove [%02X,%02X]", reply_data[0], reply_data[1]);
|
||||
|
||||
checksum = ((uint16_t) this->current_transmission_.memory_location +
|
||||
(uint16_t) this->current_transmission_.memory_address + (uint16_t) reply_data[1]) &
|
||||
0xFF;
|
||||
if (reply_data[0] != checksum) {
|
||||
ESP_LOGE(TAG, "Checksum missmatch! From [0x%02X:0x%02X] received [0x%02X,0x%02X]. Expected 0x%02X, got 0x%02X",
|
||||
this->current_transmission_.memory_location, this->current_transmission_.memory_address, reply_data[0],
|
||||
reply_data[1], checksum, reply_data[0]);
|
||||
return -1;
|
||||
}
|
||||
return ((int) reply_data[1]);
|
||||
}
|
||||
|
||||
void MicroNova::write_address(uint8_t location, uint8_t address, uint8_t data) {
|
||||
uint8_t write_data[4] = {0, 0, 0, 0};
|
||||
uint16_t checksum = 0;
|
||||
|
||||
if (this->reply_pending_mutex_.try_lock()) {
|
||||
write_data[0] = location;
|
||||
write_data[1] = address;
|
||||
write_data[2] = data;
|
||||
|
||||
checksum = ((uint16_t) write_data[0] + (uint16_t) write_data[1] + (uint16_t) write_data[2]) & 0xFF;
|
||||
write_data[3] = checksum;
|
||||
|
||||
ESP_LOGV(TAG, "Write 4 bytes [%02X,%02X,%02X,%02X]", write_data[0], write_data[1], write_data[2], write_data[3]);
|
||||
|
||||
this->enable_rx_pin_->digital_write(true);
|
||||
this->write_array(write_data, 4);
|
||||
this->flush();
|
||||
this->enable_rx_pin_->digital_write(false);
|
||||
|
||||
this->current_transmission_.request_transmission_time = millis();
|
||||
this->current_transmission_.memory_location = location;
|
||||
this->current_transmission_.memory_address = address;
|
||||
this->current_transmission_.reply_pending = true;
|
||||
this->current_transmission_.initiating_listener = nullptr;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Reply is pending, skipping write");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
164
esphome/components/micronova/micronova.h
Normal file
164
esphome/components/micronova/micronova.h
Normal file
@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
static const char *const TAG = "micronova";
|
||||
static const int STOVE_REPLY_DELAY = 60;
|
||||
|
||||
static const std::string STOVE_STATES[11] = {"Off",
|
||||
"Start",
|
||||
"Pellets loading",
|
||||
"Ignition",
|
||||
"Working",
|
||||
"Brazier Cleaning",
|
||||
"Final Cleaning",
|
||||
"Standby",
|
||||
"No pellets alarm",
|
||||
"No ignition alarm",
|
||||
"Undefined alarm"};
|
||||
|
||||
enum class MicroNovaFunctions {
|
||||
STOVE_FUNCTION_VOID = 0,
|
||||
STOVE_FUNCTION_SWITCH = 1,
|
||||
STOVE_FUNCTION_ROOM_TEMPERATURE = 2,
|
||||
STOVE_FUNCTION_THERMOSTAT_TEMPERATURE = 3,
|
||||
STOVE_FUNCTION_FUMES_TEMPERATURE = 4,
|
||||
STOVE_FUNCTION_STOVE_POWER = 5,
|
||||
STOVE_FUNCTION_FAN_SPEED = 6,
|
||||
STOVE_FUNCTION_STOVE_STATE = 7,
|
||||
STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR = 8,
|
||||
STOVE_FUNCTION_WATER_TEMPERATURE = 9,
|
||||
STOVE_FUNCTION_WATER_PRESSURE = 10,
|
||||
STOVE_FUNCTION_POWER_LEVEL = 11,
|
||||
STOVE_FUNCTION_CUSTOM = 12
|
||||
};
|
||||
|
||||
class MicroNova;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Interface classes.
|
||||
class MicroNovaBaseListener {
|
||||
public:
|
||||
MicroNovaBaseListener() {}
|
||||
MicroNovaBaseListener(MicroNova *m) { this->micronova_ = m; }
|
||||
virtual void dump_config();
|
||||
|
||||
void set_micronova_object(MicroNova *m) { this->micronova_ = m; }
|
||||
|
||||
void set_function(MicroNovaFunctions f) { this->function_ = f; }
|
||||
MicroNovaFunctions get_function() { return this->function_; }
|
||||
|
||||
void set_memory_location(uint8_t l) { this->memory_location_ = l; }
|
||||
uint8_t get_memory_location() { return this->memory_location_; }
|
||||
|
||||
void set_memory_address(uint8_t a) { this->memory_address_ = a; }
|
||||
uint8_t get_memory_address() { return this->memory_address_; }
|
||||
|
||||
protected:
|
||||
MicroNova *micronova_{nullptr};
|
||||
MicroNovaFunctions function_ = MicroNovaFunctions::STOVE_FUNCTION_VOID;
|
||||
uint8_t memory_location_ = 0;
|
||||
uint8_t memory_address_ = 0;
|
||||
};
|
||||
|
||||
class MicroNovaSensorListener : public MicroNovaBaseListener {
|
||||
public:
|
||||
MicroNovaSensorListener() {}
|
||||
MicroNovaSensorListener(MicroNova *m) : MicroNovaBaseListener(m) {}
|
||||
virtual void request_value_from_stove() = 0;
|
||||
virtual void process_value_from_stove(int value_from_stove) = 0;
|
||||
|
||||
void set_needs_update(bool u) { this->needs_update_ = u; }
|
||||
bool get_needs_update() { return this->needs_update_; }
|
||||
|
||||
protected:
|
||||
bool needs_update_ = false;
|
||||
};
|
||||
|
||||
class MicroNovaNumberListener : public MicroNovaBaseListener {
|
||||
public:
|
||||
MicroNovaNumberListener(MicroNova *m) : MicroNovaBaseListener(m) {}
|
||||
virtual void request_value_from_stove() = 0;
|
||||
virtual void process_value_from_stove(int value_from_stove) = 0;
|
||||
|
||||
void set_needs_update(bool u) { this->needs_update_ = u; }
|
||||
bool get_needs_update() { return this->needs_update_; }
|
||||
|
||||
protected:
|
||||
bool needs_update_ = false;
|
||||
};
|
||||
|
||||
class MicroNovaSwitchListener : public MicroNovaBaseListener {
|
||||
public:
|
||||
MicroNovaSwitchListener(MicroNova *m) : MicroNovaBaseListener(m) {}
|
||||
virtual void set_stove_state(bool v) = 0;
|
||||
virtual bool get_stove_state() = 0;
|
||||
|
||||
protected:
|
||||
uint8_t memory_data_on_ = 0;
|
||||
uint8_t memory_data_off_ = 0;
|
||||
};
|
||||
|
||||
class MicroNovaButtonListener : public MicroNovaBaseListener {
|
||||
public:
|
||||
MicroNovaButtonListener(MicroNova *m) : MicroNovaBaseListener(m) {}
|
||||
|
||||
protected:
|
||||
uint8_t memory_data_ = 0;
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Main component class
|
||||
class MicroNova : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
MicroNova() {}
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
void register_micronova_listener(MicroNovaSensorListener *l) { this->micronova_listeners_.push_back(l); }
|
||||
|
||||
void request_address(uint8_t location, uint8_t address, MicroNovaSensorListener *listener);
|
||||
void write_address(uint8_t location, uint8_t address, uint8_t data);
|
||||
int read_stove_reply();
|
||||
|
||||
void set_enable_rx_pin(GPIOPin *enable_rx_pin) { this->enable_rx_pin_ = enable_rx_pin; }
|
||||
|
||||
void set_current_stove_state(uint8_t s) { this->current_stove_state_ = s; }
|
||||
uint8_t get_current_stove_state() { return this->current_stove_state_; }
|
||||
|
||||
void set_stove(MicroNovaSwitchListener *s) { this->stove_switch_ = s; }
|
||||
MicroNovaSwitchListener *get_stove_switch() { return this->stove_switch_; }
|
||||
|
||||
protected:
|
||||
uint8_t current_stove_state_ = 0;
|
||||
|
||||
GPIOPin *enable_rx_pin_{nullptr};
|
||||
|
||||
struct MicroNovaSerialTransmission {
|
||||
uint32_t request_transmission_time;
|
||||
uint8_t memory_location;
|
||||
uint8_t memory_address;
|
||||
bool reply_pending;
|
||||
MicroNovaSensorListener *initiating_listener;
|
||||
};
|
||||
|
||||
Mutex reply_pending_mutex_;
|
||||
MicroNovaSerialTransmission current_transmission_;
|
||||
|
||||
std::vector<MicroNovaSensorListener *> micronova_listeners_{};
|
||||
MicroNovaSwitchListener *stove_switch_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
110
esphome/components/micronova/number/__init__.py
Normal file
110
esphome/components/micronova/number/__init__.py
Normal file
@ -0,0 +1,110 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import number
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
UNIT_CELSIUS,
|
||||
CONF_STEP,
|
||||
)
|
||||
|
||||
from .. import (
|
||||
MicroNova,
|
||||
MicroNovaFunctions,
|
||||
CONF_MICRONOVA_ID,
|
||||
CONF_MEMORY_LOCATION,
|
||||
CONF_MEMORY_ADDRESS,
|
||||
MICRONOVA_LISTENER_SCHEMA,
|
||||
micronova_ns,
|
||||
)
|
||||
|
||||
ICON_FLASH = "mdi:flash"
|
||||
|
||||
CONF_THERMOSTAT_TEMPERATURE = "thermostat_temperature"
|
||||
CONF_POWER_LEVEL = "power_level"
|
||||
CONF_MEMORY_WRITE_LOCATION = "memory_write_location"
|
||||
|
||||
MicroNovaNumber = micronova_ns.class_("MicroNovaNumber", number.Number, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(CONF_THERMOSTAT_TEMPERATURE): number.number_schema(
|
||||
MicroNovaNumber,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
)
|
||||
.extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x20, default_memory_address=0x7D
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_MEMORY_WRITE_LOCATION, default=0xA0
|
||||
): cv.hex_int_range(),
|
||||
cv.Optional(CONF_STEP, default=1.0): cv.float_range(min=0.1, max=10.0),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_POWER_LEVEL): number.number_schema(
|
||||
MicroNovaNumber,
|
||||
icon=ICON_FLASH,
|
||||
)
|
||||
.extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x20, default_memory_address=0x7F
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
{cv.Optional(CONF_MEMORY_WRITE_LOCATION, default=0xA0): cv.hex_int_range()}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
|
||||
|
||||
if thermostat_temperature_config := config.get(CONF_THERMOSTAT_TEMPERATURE):
|
||||
numb = await number.new_number(
|
||||
thermostat_temperature_config,
|
||||
min_value=0,
|
||||
max_value=40,
|
||||
step=thermostat_temperature_config.get(CONF_STEP),
|
||||
)
|
||||
cg.add(numb.set_micronova_object(mv))
|
||||
cg.add(mv.register_micronova_listener(numb))
|
||||
cg.add(
|
||||
numb.set_memory_location(
|
||||
thermostat_temperature_config[CONF_MEMORY_LOCATION]
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
numb.set_memory_address(thermostat_temperature_config[CONF_MEMORY_ADDRESS])
|
||||
)
|
||||
cg.add(
|
||||
numb.set_memory_write_location(
|
||||
thermostat_temperature_config.get(CONF_MEMORY_WRITE_LOCATION)
|
||||
)
|
||||
)
|
||||
cg.add(
|
||||
numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_THERMOSTAT_TEMPERATURE)
|
||||
)
|
||||
|
||||
if power_level_config := config.get(CONF_POWER_LEVEL):
|
||||
numb = await number.new_number(
|
||||
power_level_config,
|
||||
min_value=1,
|
||||
max_value=5,
|
||||
step=1,
|
||||
)
|
||||
cg.add(numb.set_micronova_object(mv))
|
||||
cg.add(mv.register_micronova_listener(numb))
|
||||
cg.add(numb.set_memory_location(power_level_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(numb.set_memory_address(power_level_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(
|
||||
numb.set_memory_write_location(
|
||||
power_level_config.get(CONF_MEMORY_WRITE_LOCATION)
|
||||
)
|
||||
)
|
||||
cg.add(numb.set_function(MicroNovaFunctions.STOVE_FUNCTION_POWER_LEVEL))
|
45
esphome/components/micronova/number/micronova_number.cpp
Normal file
45
esphome/components/micronova/number/micronova_number.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "micronova_number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNovaNumber::process_value_from_stove(int value_from_stove) {
|
||||
float new_sensor_value = 0;
|
||||
|
||||
if (value_from_stove == -1) {
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
|
||||
new_sensor_value = ((float) value_from_stove) * this->traits.get_step();
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
|
||||
new_sensor_value = (float) value_from_stove;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->publish_state(new_sensor_value);
|
||||
}
|
||||
|
||||
void MicroNovaNumber::control(float value) {
|
||||
uint8_t new_number = 0;
|
||||
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
|
||||
new_number = (uint8_t) (value / this->traits.get_step());
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_POWER_LEVEL:
|
||||
new_number = (uint8_t) value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->micronova_->write_address(this->memory_write_location_, this->memory_address_, new_number);
|
||||
this->micronova_->update();
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
28
esphome/components/micronova/number/micronova_number.h
Normal file
28
esphome/components/micronova/number/micronova_number.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/micronova/micronova.h"
|
||||
#include "esphome/components/number/number.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
class MicroNovaNumber : public number::Number, public MicroNovaSensorListener {
|
||||
public:
|
||||
MicroNovaNumber() {}
|
||||
MicroNovaNumber(MicroNova *m) : MicroNovaSensorListener(m) {}
|
||||
void dump_config() override { LOG_NUMBER("", "Micronova number", this); }
|
||||
void control(float value) override;
|
||||
void request_value_from_stove() override {
|
||||
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
|
||||
}
|
||||
void process_value_from_stove(int value_from_stove) override;
|
||||
|
||||
void set_memory_write_location(uint8_t l) { this->memory_write_location_ = l; }
|
||||
uint8_t get_memory_write_location() { return this->memory_write_location_; }
|
||||
|
||||
protected:
|
||||
uint8_t memory_write_location_ = 0;
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
172
esphome/components/micronova/sensor/__init__.py
Normal file
172
esphome/components/micronova/sensor/__init__.py
Normal file
@ -0,0 +1,172 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_REVOLUTIONS_PER_MINUTE,
|
||||
)
|
||||
|
||||
from .. import (
|
||||
MicroNova,
|
||||
MicroNovaFunctions,
|
||||
CONF_MICRONOVA_ID,
|
||||
CONF_MEMORY_LOCATION,
|
||||
CONF_MEMORY_ADDRESS,
|
||||
MICRONOVA_LISTENER_SCHEMA,
|
||||
micronova_ns,
|
||||
)
|
||||
|
||||
UNIT_BAR = "bar"
|
||||
|
||||
MicroNovaSensor = micronova_ns.class_("MicroNovaSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONF_ROOM_TEMPERATURE = "room_temperature"
|
||||
CONF_FUMES_TEMPERATURE = "fumes_temperature"
|
||||
CONF_STOVE_POWER = "stove_power"
|
||||
CONF_FAN_SPEED = "fan_speed"
|
||||
CONF_WATER_TEMPERATURE = "water_temperature"
|
||||
CONF_WATER_PRESSURE = "water_pressure"
|
||||
CONF_MEMORY_ADDRESS_SENSOR = "memory_address_sensor"
|
||||
CONF_FAN_RPM_OFFSET = "fan_rpm_offset"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(CONF_ROOM_TEMPERATURE): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x01
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_FUMES_TEMPERATURE): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x5A
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_STOVE_POWER): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=0,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x34
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_FAN_SPEED): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE,
|
||||
)
|
||||
.extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x37
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
{cv.Optional(CONF_FAN_RPM_OFFSET, default=0): cv.int_range(min=0, max=255)}
|
||||
),
|
||||
cv.Optional(CONF_WATER_TEMPERATURE): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x3B
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_WATER_PRESSURE): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
unit_of_measurement=UNIT_BAR,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
accuracy_decimals=1,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x3C
|
||||
)
|
||||
),
|
||||
cv.Optional(CONF_MEMORY_ADDRESS_SENSOR): sensor.sensor_schema(
|
||||
MicroNovaSensor,
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x00
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
|
||||
|
||||
if room_temperature_config := config.get(CONF_ROOM_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(room_temperature_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(room_temperature_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(room_temperature_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_ROOM_TEMPERATURE))
|
||||
|
||||
if fumes_temperature_config := config.get(CONF_FUMES_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(fumes_temperature_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(fumes_temperature_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(fumes_temperature_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FUMES_TEMPERATURE))
|
||||
|
||||
if stove_power_config := config.get(CONF_STOVE_POWER):
|
||||
sens = await sensor.new_sensor(stove_power_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(stove_power_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(stove_power_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_POWER))
|
||||
|
||||
if fan_speed_config := config.get(CONF_FAN_SPEED):
|
||||
sens = await sensor.new_sensor(fan_speed_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(fan_speed_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(fan_speed_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_FAN_SPEED))
|
||||
cg.add(sens.set_fan_speed_offset(fan_speed_config[CONF_FAN_RPM_OFFSET]))
|
||||
|
||||
if memory_address_sensor_config := config.get(CONF_MEMORY_ADDRESS_SENSOR):
|
||||
sens = await sensor.new_sensor(memory_address_sensor_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(
|
||||
sens.set_memory_location(memory_address_sensor_config[CONF_MEMORY_LOCATION])
|
||||
)
|
||||
cg.add(
|
||||
sens.set_memory_address(memory_address_sensor_config[CONF_MEMORY_ADDRESS])
|
||||
)
|
||||
cg.add(
|
||||
sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_MEMORY_ADDRESS_SENSOR)
|
||||
)
|
||||
|
||||
if water_temperature_config := config.get(CONF_WATER_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(water_temperature_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(water_temperature_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(water_temperature_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_TEMPERATURE))
|
||||
|
||||
if water_pressure_config := config.get(CONF_WATER_PRESSURE):
|
||||
sens = await sensor.new_sensor(water_pressure_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(water_pressure_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(water_pressure_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_WATER_PRESSURE))
|
35
esphome/components/micronova/sensor/micronova_sensor.cpp
Normal file
35
esphome/components/micronova/sensor/micronova_sensor.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include "micronova_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNovaSensor::process_value_from_stove(int value_from_stove) {
|
||||
if (value_from_stove == -1) {
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
float new_sensor_value = (float) value_from_stove;
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_ROOM_TEMPERATURE:
|
||||
new_sensor_value = new_sensor_value / 2;
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_THERMOSTAT_TEMPERATURE:
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_FAN_SPEED:
|
||||
new_sensor_value = new_sensor_value == 0 ? 0 : (new_sensor_value * 10) + this->fan_speed_offset_;
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_WATER_TEMPERATURE:
|
||||
new_sensor_value = new_sensor_value / 2;
|
||||
break;
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_WATER_PRESSURE:
|
||||
new_sensor_value = new_sensor_value / 10;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->publish_state(new_sensor_value);
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
27
esphome/components/micronova/sensor/micronova_sensor.h
Normal file
27
esphome/components/micronova/sensor/micronova_sensor.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/micronova/micronova.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
class MicroNovaSensor : public sensor::Sensor, public MicroNovaSensorListener {
|
||||
public:
|
||||
MicroNovaSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
|
||||
void dump_config() override { LOG_SENSOR("", "Micronova sensor", this); }
|
||||
|
||||
void request_value_from_stove() override {
|
||||
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
|
||||
}
|
||||
void process_value_from_stove(int value_from_stove) override;
|
||||
|
||||
void set_fan_speed_offset(uint8_t f) { this->fan_speed_offset_ = f; }
|
||||
uint8_t get_set_fan_speed_offset() { return this->fan_speed_offset_; }
|
||||
|
||||
protected:
|
||||
int fan_speed_offset_ = 0;
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
56
esphome/components/micronova/switch/__init__.py
Normal file
56
esphome/components/micronova/switch/__init__.py
Normal file
@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import switch
|
||||
from esphome.const import (
|
||||
ICON_POWER,
|
||||
)
|
||||
|
||||
from .. import (
|
||||
MicroNova,
|
||||
MicroNovaFunctions,
|
||||
CONF_MICRONOVA_ID,
|
||||
CONF_MEMORY_LOCATION,
|
||||
CONF_MEMORY_ADDRESS,
|
||||
MICRONOVA_LISTENER_SCHEMA,
|
||||
micronova_ns,
|
||||
)
|
||||
|
||||
CONF_STOVE = "stove"
|
||||
CONF_MEMORY_DATA_ON = "memory_data_on"
|
||||
CONF_MEMORY_DATA_OFF = "memory_data_off"
|
||||
|
||||
MicroNovaSwitch = micronova_ns.class_("MicroNovaSwitch", switch.Switch, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(CONF_STOVE): switch.switch_schema(
|
||||
MicroNovaSwitch,
|
||||
icon=ICON_POWER,
|
||||
)
|
||||
.extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x80, default_memory_address=0x21
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_MEMORY_DATA_OFF, default=0x06): cv.hex_int_range(),
|
||||
cv.Optional(CONF_MEMORY_DATA_ON, default=0x01): cv.hex_int_range(),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
|
||||
|
||||
if stove_config := config.get(CONF_STOVE):
|
||||
sw = await switch.new_switch(stove_config, mv)
|
||||
cg.add(mv.set_stove(sw))
|
||||
cg.add(sw.set_memory_location(stove_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sw.set_memory_address(stove_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sw.set_memory_data_on(stove_config[CONF_MEMORY_DATA_ON]))
|
||||
cg.add(sw.set_memory_data_off(stove_config[CONF_MEMORY_DATA_OFF]))
|
||||
cg.add(sw.set_function(MicroNovaFunctions.STOVE_FUNCTION_SWITCH))
|
33
esphome/components/micronova/switch/micronova_switch.cpp
Normal file
33
esphome/components/micronova/switch/micronova_switch.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
#include "micronova_switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNovaSwitch::write_state(bool state) {
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_SWITCH:
|
||||
if (state) {
|
||||
// Only send power-on when current state is Off
|
||||
if (this->micronova_->get_current_stove_state() == 0) {
|
||||
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_on_);
|
||||
this->publish_state(true);
|
||||
} else
|
||||
ESP_LOGW(TAG, "Unable to turn stove on, invalid state: %d", micronova_->get_current_stove_state());
|
||||
} else {
|
||||
// don't send power-off when status is Off or Final cleaning
|
||||
if (this->micronova_->get_current_stove_state() != 0 && micronova_->get_current_stove_state() != 6) {
|
||||
this->micronova_->write_address(this->memory_location_, this->memory_address_, this->memory_data_off_);
|
||||
this->publish_state(false);
|
||||
} else
|
||||
ESP_LOGW(TAG, "Unable to turn stove off, invalid state: %d", micronova_->get_current_stove_state());
|
||||
}
|
||||
this->micronova_->update();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
29
esphome/components/micronova/switch/micronova_switch.h
Normal file
29
esphome/components/micronova/switch/micronova_switch.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/micronova/micronova.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/switch/switch.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
class MicroNovaSwitch : public Component, public switch_::Switch, public MicroNovaSwitchListener {
|
||||
public:
|
||||
MicroNovaSwitch(MicroNova *m) : MicroNovaSwitchListener(m) {}
|
||||
void dump_config() override { LOG_SWITCH("", "Micronova switch", this); }
|
||||
|
||||
void set_stove_state(bool v) override { this->publish_state(v); }
|
||||
bool get_stove_state() override { return this->state; }
|
||||
|
||||
void set_memory_data_on(uint8_t f) { this->memory_data_on_ = f; }
|
||||
uint8_t get_memory_data_on() { return this->memory_data_on_; }
|
||||
|
||||
void set_memory_data_off(uint8_t f) { this->memory_data_off_ = f; }
|
||||
uint8_t get_memory_data_off() { return this->memory_data_off_; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
43
esphome/components/micronova/text_sensor/__init__.py
Normal file
43
esphome/components/micronova/text_sensor/__init__.py
Normal file
@ -0,0 +1,43 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
|
||||
from .. import (
|
||||
MicroNova,
|
||||
MicroNovaFunctions,
|
||||
CONF_MICRONOVA_ID,
|
||||
CONF_MEMORY_LOCATION,
|
||||
CONF_MEMORY_ADDRESS,
|
||||
MICRONOVA_LISTENER_SCHEMA,
|
||||
micronova_ns,
|
||||
)
|
||||
|
||||
CONF_STOVE_STATE = "stove_state"
|
||||
|
||||
MicroNovaTextSensor = micronova_ns.class_(
|
||||
"MicroNovaTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MICRONOVA_ID): cv.use_id(MicroNova),
|
||||
cv.Optional(CONF_STOVE_STATE): text_sensor.text_sensor_schema(
|
||||
MicroNovaTextSensor
|
||||
).extend(
|
||||
MICRONOVA_LISTENER_SCHEMA(
|
||||
default_memory_location=0x00, default_memory_address=0x21
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mv = await cg.get_variable(config[CONF_MICRONOVA_ID])
|
||||
|
||||
if stove_state_config := config.get(CONF_STOVE_STATE):
|
||||
sens = await text_sensor.new_text_sensor(stove_state_config, mv)
|
||||
cg.add(mv.register_micronova_listener(sens))
|
||||
cg.add(sens.set_memory_location(stove_state_config[CONF_MEMORY_LOCATION]))
|
||||
cg.add(sens.set_memory_address(stove_state_config[CONF_MEMORY_ADDRESS]))
|
||||
cg.add(sens.set_function(MicroNovaFunctions.STOVE_FUNCTION_STOVE_STATE))
|
@ -0,0 +1,31 @@
|
||||
#include "micronova_text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
void MicroNovaTextSensor::process_value_from_stove(int value_from_stove) {
|
||||
if (value_from_stove == -1) {
|
||||
this->publish_state("unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->get_function()) {
|
||||
case MicroNovaFunctions::STOVE_FUNCTION_STOVE_STATE:
|
||||
this->micronova_->set_current_stove_state(value_from_stove);
|
||||
this->publish_state(STOVE_STATES[value_from_stove]);
|
||||
// set the stove switch to on for any value but 0
|
||||
if (value_from_stove != 0 && this->micronova_->get_stove_switch() != nullptr &&
|
||||
!this->micronova_->get_stove_switch()->get_stove_state()) {
|
||||
this->micronova_->get_stove_switch()->set_stove_state(true);
|
||||
} else if (value_from_stove == 0 && this->micronova_->get_stove_switch() != nullptr &&
|
||||
this->micronova_->get_stove_switch()->get_stove_state()) {
|
||||
this->micronova_->get_stove_switch()->set_stove_state(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/micronova/micronova.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace micronova {
|
||||
|
||||
class MicroNovaTextSensor : public text_sensor::TextSensor, public MicroNovaSensorListener {
|
||||
public:
|
||||
MicroNovaTextSensor(MicroNova *m) : MicroNovaSensorListener(m) {}
|
||||
void dump_config() override { LOG_TEXT_SENSOR("", "Micronova text sensor", this); }
|
||||
void request_value_from_stove() override {
|
||||
this->micronova_->request_address(this->memory_location_, this->memory_address_, this);
|
||||
}
|
||||
void process_value_from_stove(int value_from_stove) override;
|
||||
};
|
||||
|
||||
} // namespace micronova
|
||||
} // namespace esphome
|
@ -69,7 +69,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
||||
if ((this->distance_ != nullptr) || (this->level_ != nullptr)) {
|
||||
uint32_t distance_value = this->parse_distance_(manu_data.data);
|
||||
SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data);
|
||||
ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%dmm)", quality_value, distance_value);
|
||||
ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%" PRId32 "mm)", quality_value, distance_value);
|
||||
if (quality_value < QUALITY_HIGH) {
|
||||
ESP_LOGW(TAG, "Poor read quality.");
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
|
@ -16,8 +16,8 @@ static const uint16_t MANUFACTURER_ID = 0x000D;
|
||||
void MopekaStdCheck::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Mopeka Std Check");
|
||||
ESP_LOGCONFIG(TAG, " Propane Butane mix: %.0f%%", this->propane_butane_mix_ * 100);
|
||||
ESP_LOGCONFIG(TAG, " Tank distance empty: %imm", this->empty_mm_);
|
||||
ESP_LOGCONFIG(TAG, " Tank distance full: %imm", this->full_mm_);
|
||||
ESP_LOGCONFIG(TAG, " Tank distance empty: %" PRIi32 "mm", this->empty_mm_);
|
||||
ESP_LOGCONFIG(TAG, " Tank distance full: %" PRIi32 "mm", this->full_mm_);
|
||||
LOG_SENSOR(" ", "Level", this->level_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
@ -19,7 +19,7 @@ class MQTTBackendESP8266 final : public MQTTBackend {
|
||||
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
|
||||
mqtt_client_.setWill(topic, qos, retain, payload);
|
||||
}
|
||||
void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(IPAddress(ip), port); }
|
||||
void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(ip, port); }
|
||||
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void set_secure(bool secure) { mqtt_client.setSecure(secure); }
|
||||
|
@ -136,6 +136,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
if (node_friendly_name.empty()) {
|
||||
node_friendly_name = node_name;
|
||||
}
|
||||
const std::string &node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
|
||||
device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
|
||||
@ -143,6 +144,7 @@ bool MQTTComponent::send_discovery_() {
|
||||
device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();
|
||||
device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
|
||||
device_info[MQTT_DEVICE_MANUFACTURER] = "espressif";
|
||||
device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
|
||||
},
|
||||
0, discovery_info.retain);
|
||||
}
|
||||
|
@ -3,7 +3,11 @@
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <array>
|
||||
#include "esphome/core/macros.h"
|
||||
|
||||
#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
|
||||
#include <lwip/ip_addr.h>
|
||||
#endif
|
||||
|
||||
#if USE_ARDUINO
|
||||
#include <Arduino.h>
|
||||
|
@ -116,6 +116,7 @@ void Nextion::reset_(bool reset_nextion) {
|
||||
this->read_byte(&d);
|
||||
};
|
||||
this->nextion_queue_.clear();
|
||||
this->waveform_queue_.clear();
|
||||
}
|
||||
|
||||
void Nextion::dump_config() {
|
||||
@ -364,37 +365,21 @@ void Nextion::process_nextion_commands_() {
|
||||
ESP_LOGW(TAG, "Nextion reported baud rate invalid!");
|
||||
break;
|
||||
case 0x12: // invalid Waveform ID or Channel # was used
|
||||
if (this->waveform_queue_.empty()) {
|
||||
ESP_LOGW(TAG,
|
||||
"Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!");
|
||||
} else {
|
||||
auto &nb = this->waveform_queue_.front();
|
||||
NextionComponentBase *component = nb->component;
|
||||
|
||||
if (!this->nextion_queue_.empty()) {
|
||||
int index = 0;
|
||||
int found = -1;
|
||||
for (auto &nb : this->nextion_queue_) {
|
||||
NextionComponentBase *component = nb->component;
|
||||
ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!",
|
||||
component->get_component_id(), component->get_wave_channel_id());
|
||||
|
||||
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
|
||||
ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!",
|
||||
component->get_component_id(), component->get_wave_channel_id());
|
||||
ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d",
|
||||
component->get_component_id(), component->get_wave_channel_id());
|
||||
|
||||
ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d",
|
||||
component->get_component_id(), component->get_wave_channel_id());
|
||||
|
||||
found = index;
|
||||
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
if (found != -1) {
|
||||
this->nextion_queue_.erase(this->nextion_queue_.begin() + found);
|
||||
} else {
|
||||
ESP_LOGW(
|
||||
TAG,
|
||||
"Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!");
|
||||
}
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
}
|
||||
break;
|
||||
case 0x1A: // variable name invalid
|
||||
@ -697,44 +682,29 @@ void Nextion::process_nextion_commands_() {
|
||||
}
|
||||
case 0xFD: { // data transparent transmit finished
|
||||
ESP_LOGVV(TAG, "Nextion reported data transmit finished!");
|
||||
this->check_pending_waveform_();
|
||||
break;
|
||||
}
|
||||
case 0xFE: { // data transparent transmit ready
|
||||
ESP_LOGVV(TAG, "Nextion reported ready for transmit!");
|
||||
|
||||
int index = 0;
|
||||
int found = -1;
|
||||
for (auto &nb : this->nextion_queue_) {
|
||||
auto *component = nb->component;
|
||||
if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) {
|
||||
size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size()
|
||||
: 255; // ADDT command can only send 255
|
||||
|
||||
this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
|
||||
|
||||
ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu",
|
||||
component->get_component_id(), component->get_wave_channel_id(), buffer_to_send);
|
||||
|
||||
if (component->get_wave_buffer().size() <= 255) {
|
||||
component->get_wave_buffer().clear();
|
||||
} else {
|
||||
component->get_wave_buffer().erase(component->get_wave_buffer().begin(),
|
||||
component->get_wave_buffer().begin() + buffer_to_send);
|
||||
}
|
||||
found = index;
|
||||
delete component; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
|
||||
if (found == -1) {
|
||||
if (this->waveform_queue_.empty()) {
|
||||
ESP_LOGE(TAG, "No waveforms in queue to send data!");
|
||||
break;
|
||||
} else {
|
||||
this->nextion_queue_.erase(this->nextion_queue_.begin() + found);
|
||||
}
|
||||
|
||||
auto &nb = this->waveform_queue_.front();
|
||||
auto *component = nb->component;
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
: 255; // ADDT command can only send 255
|
||||
|
||||
this->write_array(component->get_wave_buffer().data(), static_cast<int>(buffer_to_send));
|
||||
|
||||
ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu",
|
||||
component->get_component_id(), component->get_wave_channel_id(), buffer_to_send);
|
||||
|
||||
component->clear_wave_buffer(buffer_to_send);
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -1093,17 +1063,28 @@ void Nextion::add_addt_command_to_queue(NextionComponentBase *component) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion::NextionQueue *nextion_queue = new nextion::NextionQueue;
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
||||
nextion_queue->component = new nextion::NextionComponentBase;
|
||||
nextion_queue->component = component;
|
||||
nextion_queue->queue_time = millis();
|
||||
|
||||
this->waveform_queue_.push_back(nextion_queue);
|
||||
if (this->waveform_queue_.size() == 1)
|
||||
this->check_pending_waveform_();
|
||||
}
|
||||
|
||||
void Nextion::check_pending_waveform_() {
|
||||
if (this->waveform_queue_.empty())
|
||||
return;
|
||||
|
||||
auto *nb = this->waveform_queue_.front();
|
||||
auto *component = nb->component;
|
||||
size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size()
|
||||
: 255; // ADDT command can only send 255
|
||||
|
||||
std::string command = "addt " + to_string(component->get_component_id()) + "," +
|
||||
to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send);
|
||||
if (this->send_command_(command)) {
|
||||
this->nextion_queue_.push_back(nextion_queue);
|
||||
if (!this->send_command_(command)) {
|
||||
delete nb; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->waveform_queue_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -740,6 +740,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
|
||||
protected:
|
||||
std::deque<NextionQueue *> nextion_queue_;
|
||||
std::deque<NextionQueue *> waveform_queue_;
|
||||
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
||||
void all_components_send_state_(bool force_update = false);
|
||||
uint64_t comok_sent_ = 0;
|
||||
@ -780,6 +781,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
const std::string &variable_name_to_send,
|
||||
const std::string &state_value, bool is_sleep_safe = false);
|
||||
|
||||
void check_pending_waveform_();
|
||||
|
||||
#ifdef USE_NEXTION_TFT_UPLOAD
|
||||
#ifdef USE_ESP8266
|
||||
WiFiClient *wifi_client_{nullptr};
|
||||
|
@ -69,6 +69,13 @@ class NextionComponentBase {
|
||||
|
||||
std::vector<uint8_t> get_wave_buffer() { return this->wave_buffer_; }
|
||||
size_t get_wave_buffer_size() { return this->wave_buffer_.size(); }
|
||||
void clear_wave_buffer(size_t buffer_sent) {
|
||||
if (this->wave_buffer_.size() <= buffer_sent) {
|
||||
this->wave_buffer_.clear();
|
||||
} else {
|
||||
this->wave_buffer_.erase(this->wave_buffer_.begin(), this->wave_buffer_.begin() + buffer_sent);
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_variable_name() { return this->variable_name_; }
|
||||
std::string get_variable_name_to_send() { return this->variable_name_to_send_; }
|
||||
|
@ -135,7 +135,7 @@ def _process_base_package(config: dict) -> dict:
|
||||
packages[file] = new_yaml
|
||||
except EsphomeError as e:
|
||||
raise cv.Invalid(
|
||||
f"{file} is not a valid YAML file. Please check the file contents."
|
||||
f"{file} is not a valid YAML file. Please check the file contents.\n{e}"
|
||||
) from e
|
||||
return packages
|
||||
|
||||
|
@ -64,7 +64,7 @@ PCA6416A_PIN_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA)
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_PCA6416A, PCA6416A_PIN_SCHEMA)
|
||||
async def pca6416a_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_PCA6416A])
|
||||
|
@ -11,9 +11,10 @@ from esphome.const import (
|
||||
CONF_OUTPUT,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@hwstar"]
|
||||
CODEOWNERS = ["@hwstar", "@clydebarrow"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
CONF_PIN_COUNT = "pin_count"
|
||||
pca9554_ns = cg.esphome_ns.namespace("pca9554")
|
||||
|
||||
PCA9554Component = pca9554_ns.class_("PCA9554Component", cg.Component, i2c.I2CDevice)
|
||||
@ -23,7 +24,12 @@ PCA9554GPIOPin = pca9554_ns.class_(
|
||||
|
||||
CONF_PCA9554 = "pca9554"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA9554Component)})
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(PCA9554Component),
|
||||
cv.Optional(CONF_PIN_COUNT, default=8): cv.one_of(4, 8, 16),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(
|
||||
i2c.i2c_device_schema(0x20)
|
||||
@ -33,6 +39,7 @@ CONFIG_SCHEMA = (
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_pin_count(config[CONF_PIN_COUNT]))
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
@ -49,7 +56,7 @@ PCA9554_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PCA9554GPIOPin),
|
||||
cv.Required(CONF_PCA9554): cv.use_id(PCA9554Component),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=8),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15),
|
||||
cv.Optional(CONF_MODE, default={}): cv.All(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
@ -58,11 +65,19 @@ PCA9554_PIN_SCHEMA = cv.All(
|
||||
validate_mode,
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("pca9554", PCA9554_PIN_SCHEMA)
|
||||
def pca9554_pin_final_validate(pin_config, parent_config):
|
||||
count = parent_config[CONF_PIN_COUNT]
|
||||
if pin_config[CONF_NUMBER] >= count:
|
||||
raise cv.Invalid(f"Pin number must be in range 0-{count - 1}")
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(
|
||||
CONF_PCA9554, PCA9554_PIN_SCHEMA, pca9554_pin_final_validate
|
||||
)
|
||||
async def pca9554_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_PCA9554])
|
||||
|
@ -4,6 +4,7 @@
|
||||
namespace esphome {
|
||||
namespace pca9554 {
|
||||
|
||||
// for 16 bit expanders, these addresses will be doubled.
|
||||
const uint8_t INPUT_REG = 0;
|
||||
const uint8_t OUTPUT_REG = 1;
|
||||
const uint8_t INVERT_REG = 2;
|
||||
@ -13,9 +14,10 @@ static const char *const TAG = "pca9554";
|
||||
|
||||
void PCA9554Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PCA9554/PCA9554A...");
|
||||
this->reg_width_ = (this->pin_count_ + 7) / 8;
|
||||
// Test to see if device exists
|
||||
if (!this->read_inputs_()) {
|
||||
ESP_LOGE(TAG, "PCA9554 not available under 0x%02X", this->address_);
|
||||
ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@ -44,6 +46,7 @@ void PCA9554Component::loop() {
|
||||
|
||||
void PCA9554Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "PCA9554:");
|
||||
ESP_LOGCONFIG(TAG, " I/O Pins: %d", this->pin_count_);
|
||||
LOG_I2C_DEVICE(this)
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with PCA9554 failed!");
|
||||
@ -85,25 +88,33 @@ void PCA9554Component::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
}
|
||||
|
||||
bool PCA9554Component::read_inputs_() {
|
||||
uint8_t inputs;
|
||||
uint8_t inputs[2];
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGD(TAG, "Device marked failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this->last_error_ = this->read_register(INPUT_REG, &inputs, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) !=
|
||||
esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
this->input_mask_ = inputs;
|
||||
this->input_mask_ = inputs[0];
|
||||
if (this->reg_width_ == 2) {
|
||||
this->input_mask_ |= inputs[1] << 8;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PCA9554Component::write_register_(uint8_t reg, uint8_t value) {
|
||||
if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
|
||||
uint8_t outputs[2];
|
||||
outputs[0] = (uint8_t) value;
|
||||
outputs[1] = (uint8_t) (value >> 8);
|
||||
if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) !=
|
||||
esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
|
@ -28,19 +28,25 @@ class PCA9554Component : public Component, public i2c::I2CDevice {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_pin_count(size_t pin_count) { this->pin_count_ = pin_count; }
|
||||
|
||||
protected:
|
||||
bool read_inputs_();
|
||||
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
bool write_register_(uint8_t reg, uint16_t value);
|
||||
|
||||
/// number of bits the expander has
|
||||
size_t pin_count_{8};
|
||||
/// width of registers
|
||||
size_t reg_width_{1};
|
||||
/// Mask for the pin config - 1 means OUTPUT, 0 means INPUT
|
||||
uint8_t config_mask_{0x00};
|
||||
uint16_t config_mask_{0x00};
|
||||
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||
uint8_t output_mask_{0x00};
|
||||
uint16_t output_mask_{0x00};
|
||||
/// The state of the actual input pin states - 1 means HIGH, 0 means LOW
|
||||
uint8_t input_mask_{0x00};
|
||||
uint16_t input_mask_{0x00};
|
||||
/// Flags to check if read previously during this loop
|
||||
uint8_t was_previously_read_ = {0x00};
|
||||
uint16_t was_previously_read_ = {0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
};
|
||||
|
@ -65,7 +65,7 @@ PCF8574_PIN_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("pcf8574", PCF8574_PIN_SCHEMA)
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_PCF8574, PCF8574_PIN_SCHEMA)
|
||||
async def pcf8574_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_PCF8574])
|
||||
|
@ -4,7 +4,15 @@ import esphome.config_validation as cv
|
||||
import esphome.final_validate as fv
|
||||
from esphome import automation
|
||||
from esphome.components.output import FloatOutput
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||
from esphome.components.speaker import Speaker
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_OUTPUT,
|
||||
CONF_PLATFORM,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_SPEAKER,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -24,17 +32,23 @@ IsPlayingCondition = rtttl_ns.class_("IsPlayingCondition", automation.Condition)
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Rtttl),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(FloatOutput),
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FinishedPlaybackTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Rtttl),
|
||||
cv.Optional(CONF_OUTPUT): cv.use_id(FloatOutput),
|
||||
cv.Optional(CONF_SPEAKER): cv.use_id(Speaker),
|
||||
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
FinishedPlaybackTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_exactly_one_key(CONF_OUTPUT, CONF_SPEAKER),
|
||||
)
|
||||
|
||||
|
||||
def validate_parent_output_config(value):
|
||||
@ -63,9 +77,9 @@ def validate_parent_output_config(value):
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema(
|
||||
cv.Optional(CONF_OUTPUT): fv.id_declaration_match_schema(
|
||||
validate_parent_output_config
|
||||
)
|
||||
),
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)
|
||||
@ -75,8 +89,14 @@ async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
out = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(out))
|
||||
if CONF_OUTPUT in config:
|
||||
out = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(out))
|
||||
cg.add_define("USE_OUTPUT")
|
||||
|
||||
if CONF_SPEAKER in config:
|
||||
out = await cg.get_variable(config[CONF_SPEAKER])
|
||||
cg.add(var.set_speaker(out))
|
||||
|
||||
for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "rtttl.h"
|
||||
#include <cmath>
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@ -15,104 +16,185 @@ static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370,
|
||||
1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217,
|
||||
2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951};
|
||||
|
||||
static const uint16_t I2S_SPEED = 1600;
|
||||
|
||||
#undef HALF_PI
|
||||
static const double HALF_PI = 1.5707963267948966192313216916398;
|
||||
|
||||
inline double deg2rad(double degrees) {
|
||||
static const double PI_ON_180 = 4.0 * atan(1.0) / 180.0;
|
||||
return degrees * PI_ON_180;
|
||||
}
|
||||
|
||||
void Rtttl::dump_config() { ESP_LOGCONFIG(TAG, "Rtttl"); }
|
||||
|
||||
void Rtttl::play(std::string rtttl) {
|
||||
rtttl_ = std::move(rtttl);
|
||||
this->rtttl_ = std::move(rtttl);
|
||||
|
||||
this->default_duration_ = 4;
|
||||
this->default_octave_ = 6;
|
||||
this->note_duration_ = 0;
|
||||
|
||||
default_duration_ = 4;
|
||||
default_octave_ = 6;
|
||||
int bpm = 63;
|
||||
uint8_t num;
|
||||
|
||||
// Get name
|
||||
position_ = rtttl_.find(':');
|
||||
this->position_ = rtttl_.find(':');
|
||||
|
||||
// it's somewhat documented to be up to 10 characters but let's be a bit flexible here
|
||||
if (position_ == std::string::npos || position_ > 15) {
|
||||
if (this->position_ == std::string::npos || this->position_ > 15) {
|
||||
ESP_LOGE(TAG, "Missing ':' when looking for name.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = this->rtttl_.substr(0, position_);
|
||||
auto name = this->rtttl_.substr(0, this->position_);
|
||||
ESP_LOGD(TAG, "Playing song %s", name.c_str());
|
||||
|
||||
// get default duration
|
||||
position_ = this->rtttl_.find("d=", position_);
|
||||
if (position_ == std::string::npos) {
|
||||
this->position_ = this->rtttl_.find("d=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing 'd='");
|
||||
return;
|
||||
}
|
||||
position_ += 2;
|
||||
this->position_ += 2;
|
||||
num = this->get_integer_();
|
||||
if (num > 0)
|
||||
default_duration_ = num;
|
||||
this->default_duration_ = num;
|
||||
|
||||
// get default octave
|
||||
position_ = rtttl_.find("o=", position_);
|
||||
if (position_ == std::string::npos) {
|
||||
this->position_ = this->rtttl_.find("o=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing 'o=");
|
||||
return;
|
||||
}
|
||||
position_ += 2;
|
||||
this->position_ += 2;
|
||||
num = get_integer_();
|
||||
if (num >= 3 && num <= 7)
|
||||
default_octave_ = num;
|
||||
this->default_octave_ = num;
|
||||
|
||||
// get BPM
|
||||
position_ = rtttl_.find("b=", position_);
|
||||
if (position_ == std::string::npos) {
|
||||
this->position_ = this->rtttl_.find("b=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing b=");
|
||||
return;
|
||||
}
|
||||
position_ += 2;
|
||||
this->position_ += 2;
|
||||
num = get_integer_();
|
||||
if (num != 0)
|
||||
bpm = num;
|
||||
|
||||
position_ = rtttl_.find(':', position_);
|
||||
if (position_ == std::string::npos) {
|
||||
this->position_ = this->rtttl_.find(':', this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing second ':'");
|
||||
return;
|
||||
}
|
||||
position_++;
|
||||
this->position_++;
|
||||
|
||||
// BPM usually expresses the number of quarter notes per minute
|
||||
wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds)
|
||||
this->wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds)
|
||||
|
||||
output_freq_ = 0;
|
||||
last_note_ = millis();
|
||||
note_duration_ = 1;
|
||||
this->output_freq_ = 0;
|
||||
this->last_note_ = millis();
|
||||
this->note_duration_ = 1;
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
this->samples_sent_ = 0;
|
||||
this->samples_count_ = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Rtttl::stop() {
|
||||
this->note_duration_ = 0;
|
||||
#ifdef USE_OUTPUT
|
||||
if (this->output_ != nullptr) {
|
||||
this->output_->set_level(0.0);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
if (this->speaker_->is_running()) {
|
||||
this->speaker_->stop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Rtttl::loop() {
|
||||
if (note_duration_ == 0 || millis() - last_note_ < note_duration_)
|
||||
if (this->note_duration_ == 0)
|
||||
return;
|
||||
|
||||
if (!rtttl_[position_]) {
|
||||
output_->set_level(0.0);
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
if (this->samples_sent_ != this->samples_count_) {
|
||||
SpeakerSample sample[SAMPLE_BUFFER_SIZE + 1];
|
||||
int x = 0;
|
||||
double rem = 0.0;
|
||||
|
||||
while (true) {
|
||||
// Try and send out the remainder of the existing note, one per loop()
|
||||
|
||||
if (this->samples_per_wave_ != 0 && this->samples_sent_ >= this->samples_gap_) { // Play note//
|
||||
rem = ((this->samples_sent_ << 10) % this->samples_per_wave_) * (360.0 / this->samples_per_wave_);
|
||||
|
||||
int16_t val = 8192 * sin(deg2rad(rem));
|
||||
|
||||
sample[x].left = val;
|
||||
sample[x].right = val;
|
||||
|
||||
} else {
|
||||
sample[x].left = 0;
|
||||
sample[x].right = 0;
|
||||
}
|
||||
|
||||
if (x >= SAMPLE_BUFFER_SIZE || this->samples_sent_ >= this->samples_count_) {
|
||||
break;
|
||||
}
|
||||
this->samples_sent_++;
|
||||
x++;
|
||||
}
|
||||
if (x > 0) {
|
||||
int send = this->speaker_->play((uint8_t *) (&sample), x * 4);
|
||||
if (send != x * 4) {
|
||||
this->samples_sent_ -= (x - (send / 4));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_OUTPUT
|
||||
if (this->output_ != nullptr && millis() - this->last_note_ < this->note_duration_)
|
||||
return;
|
||||
#endif
|
||||
if (!this->rtttl_[position_]) {
|
||||
this->note_duration_ = 0;
|
||||
#ifdef USE_OUTPUT
|
||||
if (this->output_ != nullptr) {
|
||||
this->output_->set_level(0.0);
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG, "Playback finished");
|
||||
this->on_finished_playback_callback_.call();
|
||||
note_duration_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// align to note: most rtttl's out there does not add and space after the ',' separator but just in case...
|
||||
while (rtttl_[position_] == ',' || rtttl_[position_] == ' ')
|
||||
position_++;
|
||||
while (this->rtttl_[this->position_] == ',' || this->rtttl_[this->position_] == ' ')
|
||||
this->position_++;
|
||||
|
||||
// first, get note duration, if available
|
||||
uint8_t num = this->get_integer_();
|
||||
|
||||
if (num) {
|
||||
note_duration_ = wholenote_ / num;
|
||||
this->note_duration_ = this->wholenote_ / num;
|
||||
} else {
|
||||
note_duration_ = wholenote_ / default_duration_; // we will need to check if we are a dotted note after
|
||||
this->note_duration_ =
|
||||
this->wholenote_ / this->default_duration_; // we will need to check if we are a dotted note after
|
||||
}
|
||||
|
||||
uint8_t note;
|
||||
|
||||
switch (rtttl_[position_]) {
|
||||
switch (this->rtttl_[this->position_]) {
|
||||
case 'c':
|
||||
note = 1;
|
||||
break;
|
||||
@ -138,51 +220,81 @@ void Rtttl::loop() {
|
||||
default:
|
||||
note = 0;
|
||||
}
|
||||
position_++;
|
||||
this->position_++;
|
||||
|
||||
// now, get optional '#' sharp
|
||||
if (rtttl_[position_] == '#') {
|
||||
if (this->rtttl_[this->position_] == '#') {
|
||||
note++;
|
||||
position_++;
|
||||
this->position_++;
|
||||
}
|
||||
|
||||
// now, get optional '.' dotted note
|
||||
if (rtttl_[position_] == '.') {
|
||||
note_duration_ += note_duration_ / 2;
|
||||
position_++;
|
||||
if (this->rtttl_[this->position_] == '.') {
|
||||
this->note_duration_ += this->note_duration_ / 2;
|
||||
this->position_++;
|
||||
}
|
||||
|
||||
// now, get scale
|
||||
uint8_t scale = get_integer_();
|
||||
if (scale == 0)
|
||||
scale = default_octave_;
|
||||
scale = this->default_octave_;
|
||||
bool need_note_gap = false;
|
||||
|
||||
// Now play the note
|
||||
if (note) {
|
||||
auto note_index = (scale - 4) * 12 + note;
|
||||
if (note_index < 0 || note_index >= (int) sizeof(NOTES)) {
|
||||
ESP_LOGE(TAG, "Note out of valid range");
|
||||
this->note_duration_ = 0;
|
||||
return;
|
||||
}
|
||||
auto freq = NOTES[note_index];
|
||||
need_note_gap = freq == this->output_freq_;
|
||||
|
||||
if (freq == output_freq_) {
|
||||
// Add small silence gap between same note
|
||||
output_->set_level(0.0);
|
||||
delay(DOUBLE_NOTE_GAP_MS);
|
||||
note_duration_ -= DOUBLE_NOTE_GAP_MS;
|
||||
}
|
||||
output_freq_ = freq;
|
||||
// Add small silence gap between same note
|
||||
this->output_freq_ = freq;
|
||||
|
||||
ESP_LOGVV(TAG, "playing note: %d for %dms", note, note_duration_);
|
||||
output_->update_frequency(freq);
|
||||
output_->set_level(0.5);
|
||||
ESP_LOGVV(TAG, "playing note: %d for %dms", note, this->note_duration_);
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "waiting: %dms", note_duration_);
|
||||
output_->set_level(0.0);
|
||||
ESP_LOGVV(TAG, "waiting: %dms", this->note_duration_);
|
||||
this->output_freq_ = 0;
|
||||
}
|
||||
|
||||
last_note_ = millis();
|
||||
#ifdef USE_OUTPUT
|
||||
if (this->output_ != nullptr) {
|
||||
if (need_note_gap) {
|
||||
this->output_->set_level(0.0);
|
||||
delay(DOUBLE_NOTE_GAP_MS);
|
||||
this->note_duration_ -= DOUBLE_NOTE_GAP_MS;
|
||||
}
|
||||
if (this->output_freq_ != 0) {
|
||||
this->output_->update_frequency(this->output_freq_);
|
||||
this->output_->set_level(0.5);
|
||||
} else {
|
||||
this->output_->set_level(0.0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SPEAKER
|
||||
if (this->speaker_ != nullptr) {
|
||||
this->samples_sent_ = 0;
|
||||
this->samples_count_ = (this->sample_rate_ * this->note_duration_) / I2S_SPEED;
|
||||
// Convert from frequency in Hz to high and low samples in fixed point
|
||||
if (this->output_freq_ != 0) {
|
||||
this->samples_per_wave_ = (this->sample_rate_ << 10) / this->output_freq_;
|
||||
} else {
|
||||
this->samples_per_wave_ = 0;
|
||||
}
|
||||
if (need_note_gap) {
|
||||
this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / I2S_SPEED;
|
||||
} else {
|
||||
this->samples_gap_ = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_note_ = millis();
|
||||
}
|
||||
|
||||
} // namespace rtttl
|
||||
} // namespace esphome
|
||||
|
@ -1,23 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_OUTPUT
|
||||
#include "esphome/components/output/float_output.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
#include "esphome/components/speaker/speaker.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace rtttl {
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
static const size_t SAMPLE_BUFFER_SIZE = 256;
|
||||
|
||||
struct SpeakerSample {
|
||||
int16_t left{0};
|
||||
int16_t right{0};
|
||||
};
|
||||
#endif
|
||||
|
||||
class Rtttl : public Component {
|
||||
public:
|
||||
void set_output(output::FloatOutput *output) { output_ = output; }
|
||||
#ifdef USE_OUTPUT
|
||||
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
||||
#endif
|
||||
#ifdef USE_SPEAKER
|
||||
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||
#endif
|
||||
void play(std::string rtttl);
|
||||
void stop() {
|
||||
note_duration_ = 0;
|
||||
output_->set_level(0.0);
|
||||
}
|
||||
void stop();
|
||||
void dump_config() override;
|
||||
|
||||
bool is_playing() { return note_duration_ != 0; }
|
||||
bool is_playing() { return this->note_duration_ != 0; }
|
||||
void loop() override;
|
||||
|
||||
void add_on_finished_playback_callback(std::function<void()> callback) {
|
||||
@ -27,14 +45,14 @@ class Rtttl : public Component {
|
||||
protected:
|
||||
inline uint8_t get_integer_() {
|
||||
uint8_t ret = 0;
|
||||
while (isdigit(rtttl_[position_])) {
|
||||
ret = (ret * 10) + (rtttl_[position_++] - '0');
|
||||
while (isdigit(this->rtttl_[this->position_])) {
|
||||
ret = (ret * 10) + (this->rtttl_[this->position_++] - '0');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string rtttl_;
|
||||
size_t position_;
|
||||
std::string rtttl_{""};
|
||||
size_t position_{0};
|
||||
uint16_t wholenote_;
|
||||
uint16_t default_duration_;
|
||||
uint16_t default_octave_;
|
||||
@ -42,7 +60,22 @@ class Rtttl : public Component {
|
||||
uint16_t note_duration_;
|
||||
|
||||
uint32_t output_freq_;
|
||||
|
||||
#ifdef USE_OUTPUT
|
||||
output::FloatOutput *output_;
|
||||
#endif
|
||||
|
||||
void play_output_();
|
||||
|
||||
#ifdef USE_SPEAKER
|
||||
speaker::Speaker *speaker_;
|
||||
void play_speaker_();
|
||||
int sample_rate_{16000};
|
||||
int samples_per_wave_{0};
|
||||
int samples_sent_{0};
|
||||
int samples_count_{0};
|
||||
int samples_gap_{0};
|
||||
#endif
|
||||
|
||||
CallbackManager<void()> on_finished_playback_callback_;
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include "filter.h"
|
||||
#include <cmath>
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "sensor.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace sensor {
|
||||
@ -376,9 +376,7 @@ void OrFilter::initialize(Sensor *parent, Filter *next) {
|
||||
// TimeoutFilter
|
||||
optional<float> TimeoutFilter::new_value(float value) {
|
||||
this->set_timeout("timeout", this->time_period_, [this]() { this->output(this->value_); });
|
||||
this->output(value);
|
||||
|
||||
return {};
|
||||
return value;
|
||||
}
|
||||
|
||||
TimeoutFilter::TimeoutFilter(uint32_t time_period, float new_value) : time_period_(time_period), value_(new_value) {}
|
||||
|
@ -70,15 +70,15 @@ void SGP4xComponent::setup() {
|
||||
if (this->pref_.load(&this->voc_baselines_storage_)) {
|
||||
this->voc_state0_ = this->voc_baselines_storage_.state0;
|
||||
this->voc_state1_ = this->voc_baselines_storage_.state1;
|
||||
ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04X, state1: 0x%04X", this->voc_baselines_storage_.state0,
|
||||
voc_baselines_storage_.state1);
|
||||
ESP_LOGI(TAG, "Loaded VOC baseline state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, voc_baselines_storage_.state1);
|
||||
}
|
||||
|
||||
// Initialize storage timestamp
|
||||
this->seconds_since_last_store_ = 0;
|
||||
|
||||
if (this->voc_baselines_storage_.state0 > 0 && this->voc_baselines_storage_.state1 > 0) {
|
||||
ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04X, state1: 0x%04X",
|
||||
ESP_LOGI(TAG, "Setting VOC baseline from save state0: 0x%04" PRIX32 ", state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, voc_baselines_storage_.state1);
|
||||
voc_algorithm_.set_states(this->voc_baselines_storage_.state0, this->voc_baselines_storage_.state1);
|
||||
}
|
||||
@ -178,8 +178,8 @@ bool SGP4xComponent::measure_gas_indices_(int32_t &voc, int32_t &nox) {
|
||||
this->voc_baselines_storage_.state1 = this->voc_state1_;
|
||||
|
||||
if (this->pref_.save(&this->voc_baselines_storage_)) {
|
||||
ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04X ,state1: 0x%04X", this->voc_baselines_storage_.state0,
|
||||
voc_baselines_storage_.state1);
|
||||
ESP_LOGI(TAG, "Stored VOC baseline state0: 0x%04" PRIX32 " ,state1: 0x%04" PRIX32,
|
||||
this->voc_baselines_storage_.state0, voc_baselines_storage_.state1);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not store VOC baselines");
|
||||
}
|
||||
@ -273,7 +273,7 @@ void SGP4xComponent::update_gas_indices() {
|
||||
}
|
||||
if (this->samples_read_ < this->samples_to_stabilize_) {
|
||||
this->samples_read_++;
|
||||
ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %u", this->samples_read_,
|
||||
ESP_LOGD(TAG, "Sensor has not collected enough samples yet. (%d/%d) VOC index is: %" PRIu32, this->samples_read_,
|
||||
this->samples_to_stabilize_, this->voc_index_);
|
||||
return;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
@ -8,8 +11,6 @@
|
||||
#include <VOCGasIndexAlgorithm.h>
|
||||
#include <NOxGasIndexAlgorithm.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace sgp4x {
|
||||
|
||||
|
@ -77,7 +77,15 @@ SN74HC165_PIN_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC165, SN74HC165_PIN_SCHEMA)
|
||||
def sn74hc165_pin_final_validate(pin_config, parent_config):
|
||||
max_pins = parent_config[CONF_SR_COUNT] * 8
|
||||
if pin_config[CONF_NUMBER] >= max_pins:
|
||||
raise cv.Invalid(f"Pin number must be less than {max_pins}")
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(
|
||||
CONF_SN74HC165, SN74HC165_PIN_SCHEMA, sn74hc165_pin_final_validate
|
||||
)
|
||||
async def sn74hc165_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_parented(var, config[CONF_SN74HC165])
|
||||
|
@ -75,7 +75,15 @@ SN74HC595_PIN_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_SN74HC595, SN74HC595_PIN_SCHEMA)
|
||||
def sn74hc595_pin_final_validate(pin_config, parent_config):
|
||||
max_pins = parent_config[CONF_SR_COUNT] * 8
|
||||
if pin_config[CONF_NUMBER] >= max_pins:
|
||||
raise cv.Invalid(f"Pin number must be less than {max_pins}")
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(
|
||||
CONF_SN74HC595, SN74HC595_PIN_SCHEMA, sn74hc595_pin_final_validate
|
||||
)
|
||||
async def sn74hc595_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_parented(var, config[CONF_SN74HC595])
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user