mirror of
https://github.com/esphome/esphome.git
synced 2026-09-18 18:48:39 +00:00
Merge branch 'dev' into configure_entity
This commit is contained in:
@@ -286,6 +286,7 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Documentation Contributions:**
|
||||
* Documentation is hosted in the separate `esphome/esphome-docs` repository.
|
||||
* The contribution workflow is the same as for the codebase.
|
||||
* When editing a component's documentation page, also update the corresponding component index page to ensure both pages remain in sync.
|
||||
|
||||
* **Best Practices:**
|
||||
* **Component Development:** Keep dependencies minimal, provide clear error messages, and write comprehensive docstrings and tests.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// Used by:
|
||||
// - codeowner-review-request.yml
|
||||
// - codeowner-approved-label.yml
|
||||
// - codeowner-approved-label.yml + codeowner-approved-label-update.yml
|
||||
// - auto-label-pr/detectors.js (detectCodeOwner)
|
||||
|
||||
/**
|
||||
@@ -133,11 +133,95 @@ function loadCodeowners(repoRoot = '.') {
|
||||
return parseCodeowners(content);
|
||||
}
|
||||
|
||||
/** Possible label actions returned by determineLabelAction. */
|
||||
const LabelAction = Object.freeze({
|
||||
ADD: 'add',
|
||||
REMOVE: 'remove',
|
||||
NONE: 'none',
|
||||
});
|
||||
|
||||
/**
|
||||
* Determine what label action is needed for a PR based on codeowner approvals.
|
||||
*
|
||||
* Checks changed files against CODEOWNERS patterns, reviews, and current labels
|
||||
* to decide if the label should be added, removed, or left unchanged.
|
||||
*
|
||||
* @param {object} github - octokit instance from actions/github-script
|
||||
* @param {string} owner - repo owner
|
||||
* @param {string} repo - repo name
|
||||
* @param {number} pr_number - pull request number
|
||||
* @param {Array} codeownersPatterns - from loadCodeowners / fetchCodeowners
|
||||
* @param {string} labelName - label to manage
|
||||
* @returns {Promise<LabelAction>}
|
||||
*/
|
||||
async function determineLabelAction(github, owner, repo, pr_number, codeownersPatterns, labelName) {
|
||||
// Get the list of changed files in this PR
|
||||
const prFiles = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{ owner, repo, pull_number: pr_number }
|
||||
);
|
||||
|
||||
const changedFiles = prFiles.map(file => file.filename);
|
||||
console.log(`Found ${changedFiles.length} changed files`);
|
||||
|
||||
if (changedFiles.length === 0) {
|
||||
console.log('No changed files found');
|
||||
return LabelAction.NONE;
|
||||
}
|
||||
|
||||
// Get effective owners using last-match-wins semantics
|
||||
const effective = getEffectiveOwners(changedFiles, codeownersPatterns);
|
||||
const componentCodeowners = effective.users;
|
||||
|
||||
console.log(`Component-specific codeowners: ${Array.from(componentCodeowners).join(', ') || '(none)'}`);
|
||||
|
||||
// Get current labels
|
||||
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner, repo, issue_number: pr_number
|
||||
});
|
||||
const hasLabel = currentLabels.some(label => label.name === labelName);
|
||||
|
||||
if (componentCodeowners.size === 0) {
|
||||
console.log('No component-specific codeowners found');
|
||||
return hasLabel ? LabelAction.REMOVE : LabelAction.NONE;
|
||||
}
|
||||
|
||||
// Get all reviews and find latest per user
|
||||
const reviews = await github.paginate(
|
||||
github.rest.pulls.listReviews,
|
||||
{ owner, repo, pull_number: pr_number }
|
||||
);
|
||||
|
||||
const latestReviewByUser = new Map();
|
||||
for (const review of reviews) {
|
||||
if (!review.user || review.user.type === 'Bot' || review.state === 'COMMENTED') continue;
|
||||
latestReviewByUser.set(review.user.login, review);
|
||||
}
|
||||
|
||||
// Check if any component-specific codeowner has an active approval
|
||||
let hasCodeownerApproval = false;
|
||||
for (const [login, review] of latestReviewByUser) {
|
||||
if (review.state === 'APPROVED' && componentCodeowners.has(login)) {
|
||||
console.log(`Codeowner '${login}' has approved`);
|
||||
hasCodeownerApproval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCodeownerApproval && !hasLabel) return LabelAction.ADD;
|
||||
if (!hasCodeownerApproval && hasLabel) return LabelAction.REMOVE;
|
||||
|
||||
console.log(`Label already ${hasLabel ? 'present' : 'absent'}, no change needed`);
|
||||
return LabelAction.NONE;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
globToRegex,
|
||||
parseCodeowners,
|
||||
fetchCodeowners,
|
||||
loadCodeowners,
|
||||
classifyOwners,
|
||||
getEffectiveOwners
|
||||
getEffectiveOwners,
|
||||
LabelAction,
|
||||
determineLabelAction
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Fallback for fork PRs: phase 1 (codeowner-approved-label.yml) handles
|
||||
# non-fork PRs directly but can't write labels on fork PRs (read-only token).
|
||||
# This workflow re-determines the action and applies it if needed.
|
||||
|
||||
name: Codeowner Approved Label Update
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Codeowner Approved Label"]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update-label:
|
||||
name: Run
|
||||
if: >
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'pull_request_review'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get PR details
|
||||
id: pr
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
pr_data=$(gh pr list --repo "$REPO" --state open --search "$HEAD_SHA" \
|
||||
--json number,baseRefName --jq '.[0] // empty')
|
||||
|
||||
if [ -z "$pr_data" ]; then
|
||||
echo "No open PR found for SHA $HEAD_SHA, skipping"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
pr_number=$(echo "$pr_data" | jq -r '.number')
|
||||
base_ref=$(echo "$pr_data" | jq -r '.baseRefName')
|
||||
|
||||
echo "pr_number=$pr_number" >> "$GITHUB_OUTPUT"
|
||||
echo "base_ref=$base_ref" >> "$GITHUB_OUTPUT"
|
||||
echo "Found PR #$pr_number targeting $base_ref"
|
||||
|
||||
- name: Checkout base repository
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
ref: ${{ steps.pr.outputs.base_ref }}
|
||||
sparse-checkout: |
|
||||
.github/scripts/codeowners.js
|
||||
CODEOWNERS
|
||||
|
||||
- name: Update label
|
||||
if: steps.pr.outputs.skip != 'true'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
with:
|
||||
script: |
|
||||
const { loadCodeowners, determineLabelAction, LabelAction } = require('./.github/scripts/codeowners.js');
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pr_number = parseInt(process.env.PR_NUMBER, 10);
|
||||
const LABEL_NAME = 'code-owner-approved';
|
||||
|
||||
console.log(`Processing PR #${pr_number} for codeowner approval label`);
|
||||
|
||||
const codeownersPatterns = loadCodeowners();
|
||||
const action = await determineLabelAction(
|
||||
github, owner, repo, pr_number, codeownersPatterns, LABEL_NAME
|
||||
);
|
||||
|
||||
if (action === LabelAction.ADD) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number: pr_number, labels: [LABEL_NAME]
|
||||
});
|
||||
console.log(`Added '${LABEL_NAME}' label`);
|
||||
} else if (action === LabelAction.REMOVE) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: pr_number, name: LABEL_NAME
|
||||
});
|
||||
console.log(`Removed '${LABEL_NAME}' label`);
|
||||
} catch (error) {
|
||||
if (error.status !== 404) throw error;
|
||||
}
|
||||
} else {
|
||||
console.log('No label change needed');
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
# This workflow adds/removes a 'code-owner-approved' label when a
|
||||
# component-specific codeowner approves (or dismisses) a PR.
|
||||
# This helps maintainers prioritize PRs that have codeowner sign-off.
|
||||
# Adds/removes a 'code-owner-approved' label when a component-specific
|
||||
# codeowner approves (or dismisses) a PR.
|
||||
#
|
||||
# Only component-specific codeowners count — the catch-all @esphome/core
|
||||
# team is excluded so the label reflects domain-expert approval.
|
||||
# Handles non-fork PRs directly. For fork PRs the GITHUB_TOKEN is read-only,
|
||||
# so label writes are deferred to codeowner-approved-label-update.yml which
|
||||
# triggers via workflow_run with write permissions.
|
||||
|
||||
name: Codeowner Approved Label
|
||||
|
||||
@@ -12,7 +12,8 @@ on:
|
||||
types: [submitted, dismissed]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
pull-requests: read
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
@@ -25,134 +26,53 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
sparse-checkout: |
|
||||
.github/scripts/codeowners.js
|
||||
CODEOWNERS
|
||||
|
||||
- name: Check codeowner approval and update label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
with:
|
||||
script: |
|
||||
const { loadCodeowners, getEffectiveOwners } = require('./.github/scripts/codeowners.js');
|
||||
const { loadCodeowners, determineLabelAction, LabelAction } = require('./.github/scripts/codeowners.js');
|
||||
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
const pr_number = context.payload.pull_request.number;
|
||||
const pr_number = parseInt(process.env.PR_NUMBER, 10);
|
||||
const LABEL_NAME = 'code-owner-approved';
|
||||
|
||||
console.log(`Processing PR #${pr_number} for codeowner approval label`);
|
||||
|
||||
const codeownersPatterns = loadCodeowners();
|
||||
const action = await determineLabelAction(
|
||||
github, owner, repo, pr_number, codeownersPatterns, LABEL_NAME
|
||||
);
|
||||
|
||||
if (action === LabelAction.NONE) {
|
||||
console.log('No label change needed');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the list of changed files in this PR (with pagination)
|
||||
const prFiles = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr_number
|
||||
}
|
||||
);
|
||||
|
||||
const changedFiles = prFiles.map(file => file.filename);
|
||||
console.log(`Found ${changedFiles.length} changed files`);
|
||||
|
||||
if (changedFiles.length === 0) {
|
||||
console.log('No changed files found, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse CODEOWNERS from the checked-out base branch
|
||||
const codeownersPatterns = loadCodeowners();
|
||||
|
||||
// Get effective owners using last-match-wins semantics
|
||||
const effective = getEffectiveOwners(changedFiles, codeownersPatterns);
|
||||
|
||||
// Only keep individual component-specific codeowners (exclude teams)
|
||||
const componentCodeowners = effective.users;
|
||||
|
||||
console.log(`Component-specific codeowners for changed files: ${Array.from(componentCodeowners).join(', ') || '(none)'}`);
|
||||
|
||||
if (componentCodeowners.size === 0) {
|
||||
console.log('No component-specific codeowners found for changed files');
|
||||
// Remove label if present since there are no component codeowners
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
name: LABEL_NAME
|
||||
});
|
||||
console.log(`Removed '${LABEL_NAME}' label (no component codeowners)`);
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
console.log(`Failed to remove label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all reviews on the PR
|
||||
const reviews = await github.paginate(
|
||||
github.rest.pulls.listReviews,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr_number
|
||||
}
|
||||
);
|
||||
|
||||
// Get the latest review per user (reviews are returned chronologically)
|
||||
const latestReviewByUser = new Map();
|
||||
for (const review of reviews) {
|
||||
// Skip bot reviews and comment-only reviews
|
||||
if (!review.user || review.user.type === 'Bot' || review.state === 'COMMENTED') continue;
|
||||
latestReviewByUser.set(review.user.login, review);
|
||||
}
|
||||
|
||||
// Check if any component-specific codeowner has an active approval
|
||||
let hasCodeownerApproval = false;
|
||||
for (const [login, review] of latestReviewByUser) {
|
||||
if (review.state === 'APPROVED' && componentCodeowners.has(login)) {
|
||||
console.log(`Codeowner '${login}' has approved`);
|
||||
hasCodeownerApproval = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current labels to check if label is already present
|
||||
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number
|
||||
});
|
||||
const hasLabel = currentLabels.some(label => label.name === LABEL_NAME);
|
||||
|
||||
if (hasCodeownerApproval && !hasLabel) {
|
||||
// Add the label
|
||||
if (action === LabelAction.ADD) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
labels: [LABEL_NAME]
|
||||
owner, repo, issue_number: pr_number, labels: [LABEL_NAME]
|
||||
});
|
||||
console.log(`Added '${LABEL_NAME}' label`);
|
||||
} else if (!hasCodeownerApproval && hasLabel) {
|
||||
// Remove the label
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
name: LABEL_NAME
|
||||
});
|
||||
console.log(`Removed '${LABEL_NAME}' label`);
|
||||
} catch (error) {
|
||||
if (error.status !== 404) {
|
||||
console.log(`Failed to remove label: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`Label already ${hasLabel ? 'present' : 'absent'}, no change needed`);
|
||||
} else if (action === LabelAction.REMOVE) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number: pr_number, name: LABEL_NAME
|
||||
});
|
||||
console.log(`Removed '${LABEL_NAME}' label`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
core.setFailed(`Failed to process codeowner approval label: ${error.message}`);
|
||||
if (error.status === 403) {
|
||||
console.log('Fork PR: deferring label write to phase 2 workflow');
|
||||
} else if (error.status === 404) {
|
||||
console.log('Label already removed');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,15 +99,15 @@ jobs:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -178,17 +178,17 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USER }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the GitHub container registry
|
||||
if: matrix.registry == 'ghcr'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -54,6 +54,7 @@ esphome/components/atm90e32/* @circuitsetup @descipher
|
||||
esphome/components/audio/* @kahrendt
|
||||
esphome/components/audio_adc/* @kbx81
|
||||
esphome/components/audio_dac/* @kbx81
|
||||
esphome/components/audio_file/* @kahrendt
|
||||
esphome/components/axs15231/* @clydebarrow
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
|
||||
+4
-3
@@ -23,6 +23,7 @@ import esphome.codegen as cg
|
||||
from esphome.config import iter_component_configs, read_config, strip_default_ids
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
ARGUMENT_HELP_DEVICE,
|
||||
CONF_API,
|
||||
CONF_BAUD_RATE,
|
||||
CONF_BROKER,
|
||||
@@ -1367,7 +1368,7 @@ def parse_args(argv):
|
||||
parser_upload.add_argument(
|
||||
"--device",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
help=ARGUMENT_HELP_DEVICE,
|
||||
)
|
||||
parser_upload.add_argument(
|
||||
"--upload_speed",
|
||||
@@ -1390,7 +1391,7 @@ def parse_args(argv):
|
||||
parser_logs.add_argument(
|
||||
"--device",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
help=ARGUMENT_HELP_DEVICE,
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--reset",
|
||||
@@ -1420,7 +1421,7 @@ def parse_args(argv):
|
||||
parser_run.add_argument(
|
||||
"--device",
|
||||
action="append",
|
||||
help="Manually specify the serial port/address to use, for example /dev/ttyUSB0. Can be specified multiple times for fallback addresses.",
|
||||
help=ARGUMENT_HELP_DEVICE,
|
||||
)
|
||||
parser_run.add_argument(
|
||||
"--upload_speed",
|
||||
|
||||
@@ -125,7 +125,7 @@ void Alpha3::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc
|
||||
this->current_sensor_->publish_state(NAN);
|
||||
if (this->speed_sensor_ != nullptr)
|
||||
this->speed_sensor_->publish_state(NAN);
|
||||
if (this->speed_sensor_ != nullptr)
|
||||
if (this->voltage_sensor_ != nullptr)
|
||||
this->voltage_sensor_->publish_state(NAN);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,9 @@ void Am43Component::control(const CoverCall &call) {
|
||||
ESP_LOGW(TAG, "[%s] Error writing stop command to device, error = %d", this->get_name().c_str(), status);
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
auto opt_pos = call.get_position();
|
||||
if (opt_pos.has_value()) {
|
||||
auto pos = *opt_pos;
|
||||
|
||||
if (this->invert_position_)
|
||||
pos = 1 - pos;
|
||||
|
||||
@@ -24,8 +24,9 @@ void Anova::loop() {
|
||||
}
|
||||
|
||||
void Anova::control(const ClimateCall &call) {
|
||||
if (call.get_mode().has_value()) {
|
||||
ClimateMode mode = *call.get_mode();
|
||||
auto mode_val = call.get_mode();
|
||||
if (mode_val.has_value()) {
|
||||
ClimateMode mode = *mode_val;
|
||||
AnovaPacket *pkt;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
@@ -45,8 +46,9 @@ void Anova::control(const ClimateCall &call) {
|
||||
ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str(), status);
|
||||
}
|
||||
}
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature());
|
||||
auto target_temp = call.get_target_temperature();
|
||||
if (target_temp.has_value()) {
|
||||
auto *pkt = this->codec_->get_set_target_temp_request(*target_temp);
|
||||
auto status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->char_handle_,
|
||||
pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
|
||||
@@ -114,9 +114,10 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
}
|
||||
#elif defined(USE_API_PLAINTEXT)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
this->helper_ = std::unique_ptr<APIPlaintextFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
||||
#elif defined(USE_API_NOISE)
|
||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
this->helper_ =
|
||||
std::unique_ptr<APINoiseFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
||||
#else
|
||||
#error "No frame helper defined"
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_API
|
||||
#include "api_frame_helper.h"
|
||||
#ifdef USE_API_NOISE
|
||||
#include "api_frame_helper_noise.h"
|
||||
#endif
|
||||
#ifdef USE_API_PLAINTEXT
|
||||
#include "api_frame_helper_plaintext.h"
|
||||
#endif
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "api_server.h"
|
||||
@@ -489,7 +495,13 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
// === Optimal member ordering for 32-bit systems ===
|
||||
|
||||
// Group 1: Pointers (4 bytes each on 32-bit)
|
||||
#if defined(USE_API_NOISE) && defined(USE_API_PLAINTEXT)
|
||||
std::unique_ptr<APIFrameHelper> helper_;
|
||||
#elif defined(USE_API_NOISE)
|
||||
std::unique_ptr<APINoiseFrameHelper> helper_;
|
||||
#elif defined(USE_API_PLAINTEXT)
|
||||
std::unique_ptr<APIPlaintextFrameHelper> helper_;
|
||||
#endif
|
||||
APIServer *parent_;
|
||||
|
||||
// Group 2: Iterator union (saves ~16 bytes vs separate iterators)
|
||||
|
||||
@@ -61,6 +61,10 @@ optional<ParseResult> ATCMiThermometer::parse_header_(const esp32_ble_tracker::S
|
||||
}
|
||||
|
||||
auto raw = service_data.data;
|
||||
if (raw.size() < 13) {
|
||||
ESP_LOGVV(TAG, "parse_header_(): service data too short (%zu).", raw.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
static uint8_t last_frame_count = 0;
|
||||
if (last_frame_count == raw[12]) {
|
||||
|
||||
@@ -197,7 +197,7 @@ float ATM90E26Component::get_reactive_power_() {
|
||||
float ATM90E26Component::get_power_factor_() {
|
||||
const uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed
|
||||
if (val & 0x8000) {
|
||||
return -(val & 0x7FF) / 1000.0f;
|
||||
return -(val & 0x7FFF) / 1000.0f;
|
||||
} else {
|
||||
return val / 1000.0f;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
from dataclasses import dataclass, field
|
||||
import hashlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import puremagic
|
||||
|
||||
from esphome import external_files
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FILE,
|
||||
CONF_ID,
|
||||
CONF_PATH,
|
||||
CONF_RAW_DATA_ID,
|
||||
CONF_TYPE,
|
||||
CONF_URL,
|
||||
)
|
||||
from esphome.core import CORE, ID, HexInt
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.external_files import download_content
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
|
||||
AUTO_LOAD = ["audio"]
|
||||
|
||||
DOMAIN = "audio_file"
|
||||
|
||||
audio_file_ns = cg.esphome_ns.namespace("audio_file")
|
||||
|
||||
TYPE_LOCAL = "local"
|
||||
TYPE_WEB = "web"
|
||||
|
||||
|
||||
@dataclass
|
||||
class AudioFileData:
|
||||
file_ids: dict[str, ID] = field(default_factory=dict)
|
||||
file_cache: dict[str, tuple[bytes, MockObj]] = field(default_factory=dict)
|
||||
|
||||
|
||||
def _get_data() -> AudioFileData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = AudioFileData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def get_audio_file_ids() -> dict[str, ID]:
|
||||
"""Get all registered audio file IDs for cross-component access."""
|
||||
return _get_data().file_ids
|
||||
|
||||
|
||||
def _compute_local_file_path(value: ConfigType) -> Path:
|
||||
url = value[CONF_URL]
|
||||
h = hashlib.new("sha256")
|
||||
h.update(url.encode())
|
||||
key = h.hexdigest()[:8]
|
||||
base_dir = external_files.compute_local_file_dir(DOMAIN)
|
||||
_LOGGER.debug("_compute_local_file_path: base_dir=%s", base_dir / key)
|
||||
return base_dir / key
|
||||
|
||||
|
||||
def _download_web_file(value: ConfigType) -> ConfigType:
|
||||
url = value[CONF_URL]
|
||||
path = _compute_local_file_path(value)
|
||||
|
||||
download_content(url, path)
|
||||
_LOGGER.debug("download_web_file: path=%s", path)
|
||||
return value
|
||||
|
||||
|
||||
def _file_schema(value: ConfigType | str) -> ConfigType:
|
||||
if isinstance(value, str):
|
||||
return _validate_file_shorthand(value)
|
||||
return TYPED_FILE_SCHEMA(value)
|
||||
|
||||
|
||||
def _validate_file_shorthand(value: str) -> ConfigType:
|
||||
value = cv.string_strict(value)
|
||||
if value.startswith("http://") or value.startswith("https://"):
|
||||
return _file_schema(
|
||||
{
|
||||
CONF_TYPE: TYPE_WEB,
|
||||
CONF_URL: value,
|
||||
}
|
||||
)
|
||||
return _file_schema(
|
||||
{
|
||||
CONF_TYPE: TYPE_LOCAL,
|
||||
CONF_PATH: value,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def read_audio_file_and_type(file_config: ConfigType) -> tuple[bytes, MockObj]:
|
||||
"""Read an audio file and determine its type. Used by this component and media_source platform."""
|
||||
conf_file = file_config[CONF_FILE]
|
||||
file_source = conf_file[CONF_TYPE]
|
||||
if file_source == TYPE_LOCAL:
|
||||
path = CORE.relative_config_path(conf_file[CONF_PATH])
|
||||
elif file_source == TYPE_WEB:
|
||||
path = _compute_local_file_path(conf_file)
|
||||
else:
|
||||
raise cv.Invalid("Unsupported file source")
|
||||
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
try:
|
||||
file_type: str = puremagic.from_string(data)
|
||||
file_type = file_type.removeprefix(".")
|
||||
except puremagic.PureError as e:
|
||||
raise cv.Invalid(
|
||||
f"Unable to determine audio file type of '{path}'. "
|
||||
f"Try re-encoding the file into a supported format. Details: {e}"
|
||||
)
|
||||
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
|
||||
if file_type == "wav":
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["WAV"]
|
||||
elif file_type in ("mp3", "mpeg", "mpga"):
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["MP3"]
|
||||
elif file_type == "flac":
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["FLAC"]
|
||||
elif (
|
||||
file_type == "ogg"
|
||||
and len(data) >= 36
|
||||
and data.startswith(b"OggS")
|
||||
and data[28:36] == b"OpusHead"
|
||||
):
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["OPUS"]
|
||||
|
||||
return data, media_file_type
|
||||
|
||||
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
}
|
||||
)
|
||||
|
||||
WEB_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_URL): cv.url,
|
||||
},
|
||||
_download_web_file,
|
||||
)
|
||||
|
||||
|
||||
TYPED_FILE_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||
TYPE_WEB: WEB_SCHEMA,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
MEDIA_FILE_TYPE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(audio.AudioFile),
|
||||
cv.Required(CONF_FILE): _file_schema,
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
MAX_FILE_SIZE = 5 * 1024 * 1024 # 5 MB
|
||||
|
||||
|
||||
def _validate_supported_local_file(config: list[ConfigType]) -> list[ConfigType]:
|
||||
for file_config in config:
|
||||
data, media_file_type = read_audio_file_and_type(file_config)
|
||||
|
||||
if len(data) > MAX_FILE_SIZE:
|
||||
file_info = file_config.get(CONF_FILE, {})
|
||||
source = (
|
||||
file_info.get(CONF_PATH) or file_info.get(CONF_URL) or "unknown source"
|
||||
)
|
||||
raise cv.Invalid(
|
||||
f"Audio file {source!r} is too large ({len(data)} bytes, max {MAX_FILE_SIZE} bytes)"
|
||||
)
|
||||
|
||||
if str(media_file_type) == str(audio.AUDIO_FILE_TYPE_ENUM["NONE"]):
|
||||
file_info = file_config.get(CONF_FILE, {})
|
||||
source = (
|
||||
file_info.get(CONF_PATH) or file_info.get(CONF_URL) or "unknown source"
|
||||
)
|
||||
raise cv.Invalid(
|
||||
f"Unsupported media file from {source!r} (detected type: {media_file_type})"
|
||||
)
|
||||
|
||||
# Cache the file data so to_code() doesn't need to re-read it
|
||||
_get_data().file_cache[str(file_config[CONF_ID])] = (data, media_file_type)
|
||||
|
||||
media_file_type_str = str(media_file_type)
|
||||
if media_file_type_str == str(audio.AUDIO_FILE_TYPE_ENUM["FLAC"]):
|
||||
audio.request_flac_support()
|
||||
elif media_file_type_str == str(audio.AUDIO_FILE_TYPE_ENUM["MP3"]):
|
||||
audio.request_mp3_support()
|
||||
elif media_file_type_str == str(audio.AUDIO_FILE_TYPE_ENUM["OPUS"]):
|
||||
audio.request_opus_support()
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.ensure_list(MEDIA_FILE_TYPE_SCHEMA),
|
||||
_validate_supported_local_file,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: list[ConfigType]) -> None:
|
||||
cache = _get_data().file_cache
|
||||
|
||||
for file_config in config:
|
||||
file_id = str(file_config[CONF_ID])
|
||||
data, media_file_type = cache[file_id]
|
||||
|
||||
rhs = [HexInt(x) for x in data]
|
||||
prog_arr = cg.progmem_array(file_config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
media_files_struct = cg.StructInitializer(
|
||||
audio.AudioFile,
|
||||
(
|
||||
"data",
|
||||
prog_arr,
|
||||
),
|
||||
(
|
||||
"length",
|
||||
len(rhs),
|
||||
),
|
||||
(
|
||||
"file_type",
|
||||
media_file_type,
|
||||
),
|
||||
)
|
||||
|
||||
cg.new_Pvariable(
|
||||
file_config[CONF_ID],
|
||||
media_files_struct,
|
||||
)
|
||||
|
||||
# Store file ID for cross-component access
|
||||
_get_data().file_ids[file_id] = file_config[CONF_ID]
|
||||
|
||||
# Register all files in the shared C++ registry
|
||||
cg.add_define("AUDIO_FILE_MAX_FILES", len(config))
|
||||
for file_config in config:
|
||||
file_id = str(file_config[CONF_ID])
|
||||
file_var = await cg.get_variable(file_config[CONF_ID])
|
||||
cg.add(audio_file_ns.add_named_audio_file(file_var, file_id))
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef AUDIO_FILE_MAX_FILES
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::audio_file {
|
||||
|
||||
struct NamedAudioFile {
|
||||
audio::AudioFile *file;
|
||||
const char *file_id;
|
||||
};
|
||||
|
||||
inline StaticVector<NamedAudioFile, AUDIO_FILE_MAX_FILES>
|
||||
named_audio_files; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
inline void add_named_audio_file(audio::AudioFile *file, const char *file_id) {
|
||||
named_audio_files.push_back({file, file_id});
|
||||
}
|
||||
|
||||
inline const StaticVector<NamedAudioFile, AUDIO_FILE_MAX_FILES> &get_named_audio_files() { return named_audio_files; }
|
||||
|
||||
} // namespace esphome::audio_file
|
||||
|
||||
#endif // AUDIO_FILE_MAX_FILES
|
||||
@@ -47,7 +47,7 @@ void BalluClimate::transmit_state() {
|
||||
remote_state[11] = 0x1e;
|
||||
|
||||
// Fan speed
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state[4] |= BALLU_FAN_HIGH;
|
||||
break;
|
||||
|
||||
@@ -45,17 +45,21 @@ void BangBangClimate::setup() {
|
||||
}
|
||||
|
||||
void BangBangClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode().has_value()) {
|
||||
this->mode = *call.get_mode();
|
||||
auto mode = call.get_mode();
|
||||
if (mode.has_value()) {
|
||||
this->mode = *mode;
|
||||
}
|
||||
if (call.get_target_temperature_low().has_value()) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
auto target_temperature_low = call.get_target_temperature_low();
|
||||
if (target_temperature_low.has_value()) {
|
||||
this->target_temperature_low = *target_temperature_low;
|
||||
}
|
||||
if (call.get_target_temperature_high().has_value()) {
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
auto target_temperature_high = call.get_target_temperature_high();
|
||||
if (target_temperature_high.has_value()) {
|
||||
this->target_temperature_high = *target_temperature_high;
|
||||
}
|
||||
if (call.get_preset().has_value()) {
|
||||
this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY);
|
||||
auto preset = call.get_preset();
|
||||
if (preset.has_value()) {
|
||||
this->change_away_(*preset == climate::CLIMATE_PRESET_AWAY);
|
||||
}
|
||||
|
||||
this->compute_state_();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "bedjet_codec.h"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
@@ -68,6 +69,10 @@ BedjetPacket *BedjetCodec::get_set_runtime_remaining_request(const uint8_t hour,
|
||||
|
||||
/** Decodes the extra bytes that were received after being notified with a partial packet. */
|
||||
void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
|
||||
if (length < 5) {
|
||||
ESP_LOGVV(TAG, "Received extra: %d bytes (too short)", length);
|
||||
return;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Received extra: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
|
||||
uint8_t offset = this->last_buffer_size_;
|
||||
if (offset > 0 && length + offset <= sizeof(BedjetStatusPacket)) {
|
||||
@@ -90,14 +95,19 @@ void BedjetCodec::decode_extra(const uint8_t *data, uint16_t length) {
|
||||
* @return `true` if the packet was decoded and represents a "partial" packet; `false` otherwise.
|
||||
*/
|
||||
bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
|
||||
if (length < 5) {
|
||||
ESP_LOGW(TAG, "Received short packet: %d bytes", length);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "Received: %d bytes: %d %d %d %d", length, data[1], data[2], data[3], data[4]);
|
||||
|
||||
if (data[1] == PACKET_FORMAT_V3_HOME && data[3] == PACKET_TYPE_STATUS) {
|
||||
// Clear old buffer
|
||||
memset(&this->buf_, 0, sizeof(BedjetStatusPacket));
|
||||
// Copy new data into buffer
|
||||
memcpy(&this->buf_, data, length);
|
||||
this->last_buffer_size_ = length;
|
||||
size_t copy_len = std::min(static_cast<size_t>(length), sizeof(BedjetStatusPacket));
|
||||
memcpy(&this->buf_, data, copy_len);
|
||||
this->last_buffer_size_ = copy_len;
|
||||
|
||||
// TODO: validate the packet checksum?
|
||||
if (this->buf_.mode < 7 && this->buf_.target_temp_step >= 38 && this->buf_.target_temp_step <= 86 &&
|
||||
@@ -113,13 +123,15 @@ bool BedjetCodec::decode_notify(const uint8_t *data, uint16_t length) {
|
||||
}
|
||||
} else if (data[1] == PACKET_FORMAT_DEBUG || data[3] == PACKET_TYPE_DEBUG) {
|
||||
// We don't actually know the packet format for this. Dump packets to log, in case a pattern presents itself.
|
||||
ESP_LOGVV(TAG,
|
||||
"received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
|
||||
"[12]=%d, [-1]=%d",
|
||||
bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8],
|
||||
data[9], data[10], data[11], data[12], data[length - 1]);
|
||||
if (length >= 13) {
|
||||
ESP_LOGVV(TAG,
|
||||
"received DEBUG packet: set1=%01fF, set2=%01fF, air=%01fF; [7]=%d, [8]=%d, [9]=%d, [10]=%d, [11]=%d, "
|
||||
"[12]=%d, [-1]=%d",
|
||||
bedjet_temp_to_f(data[4]), bedjet_temp_to_f(data[5]), bedjet_temp_to_f(data[6]), data[7], data[8],
|
||||
data[9], data[10], data[11], data[12], data[length - 1]);
|
||||
}
|
||||
|
||||
if (this->has_status()) {
|
||||
if (this->has_status() && length >= 7) {
|
||||
this->status_packet_->ambient_temp_step = data[6];
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -96,8 +96,9 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (call.get_mode().has_value()) {
|
||||
ClimateMode mode = *call.get_mode();
|
||||
auto mode_opt = call.get_mode();
|
||||
if (mode_opt.has_value()) {
|
||||
ClimateMode mode = *mode_opt;
|
||||
bool button_result;
|
||||
switch (mode) {
|
||||
case CLIMATE_MODE_OFF:
|
||||
@@ -125,8 +126,9 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
auto target_temp = *call.get_target_temperature();
|
||||
auto target_temp_opt = call.get_target_temperature();
|
||||
if (target_temp_opt.has_value()) {
|
||||
auto target_temp = *target_temp_opt;
|
||||
auto result = this->parent_->set_target_temp(target_temp);
|
||||
|
||||
if (result) {
|
||||
@@ -134,8 +136,9 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_preset().has_value()) {
|
||||
ClimatePreset preset = *call.get_preset();
|
||||
auto preset_opt = call.get_preset();
|
||||
if (preset_opt.has_value()) {
|
||||
ClimatePreset preset = *preset_opt;
|
||||
bool result;
|
||||
|
||||
if (preset == CLIMATE_PRESET_BOOST) {
|
||||
@@ -187,10 +190,11 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
auto fan_mode_opt = call.get_fan_mode();
|
||||
if (fan_mode_opt.has_value()) {
|
||||
// Climate fan mode only supports low/med/high, but the BedJet supports 5-100% increments.
|
||||
// We can still support a ClimateCall that requests low/med/high, and just translate it to a step increment here.
|
||||
auto fan_mode = *call.get_fan_mode();
|
||||
auto fan_mode = *fan_mode_opt;
|
||||
bool result;
|
||||
if (fan_mode == CLIMATE_FAN_LOW) {
|
||||
result = this->parent_->set_fan_speed(20);
|
||||
|
||||
@@ -19,7 +19,8 @@ void BedJetFan::control(const fan::FanCall &call) {
|
||||
}
|
||||
bool did_change = false;
|
||||
|
||||
if (call.get_state().has_value() && this->state != *call.get_state()) {
|
||||
auto state_opt = call.get_state();
|
||||
if (state_opt.has_value() && this->state != *state_opt) {
|
||||
// Turning off is easy:
|
||||
if (this->state && this->parent_->button_off()) {
|
||||
this->state = false;
|
||||
@@ -36,8 +37,9 @@ void BedJetFan::control(const fan::FanCall &call) {
|
||||
}
|
||||
|
||||
// ignore speed changes if not on or turning on
|
||||
if (this->state && call.get_speed().has_value()) {
|
||||
auto speed = *call.get_speed();
|
||||
auto speed_opt = call.get_speed();
|
||||
if (this->state && speed_opt.has_value()) {
|
||||
auto speed = *speed_opt;
|
||||
if (speed >= 1) {
|
||||
this->speed = speed;
|
||||
// Fan.speed is 1-20, but Bedjet expects 0-19, so subtract 1
|
||||
|
||||
@@ -18,12 +18,15 @@ fan::FanTraits BinaryFan::get_traits() {
|
||||
return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0);
|
||||
}
|
||||
void BinaryFan::control(const fan::FanCall &call) {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
auto state = call.get_state();
|
||||
if (state.has_value())
|
||||
this->state = *state;
|
||||
auto oscillating = call.get_oscillating();
|
||||
if (oscillating.has_value())
|
||||
this->oscillating = *oscillating;
|
||||
auto direction = call.get_direction();
|
||||
if (direction.has_value())
|
||||
this->direction = *direction;
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
|
||||
@@ -76,11 +76,12 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_IBEACON_UUID:
|
||||
if (!device.get_ibeacon().has_value()) {
|
||||
auto maybe_ibeacon = device.get_ibeacon();
|
||||
if (!maybe_ibeacon.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ibeacon = device.get_ibeacon().value();
|
||||
auto ibeacon = *maybe_ibeacon;
|
||||
|
||||
if (this->ibeacon_uuid_ != ibeacon.get_uuid()) {
|
||||
return false;
|
||||
|
||||
@@ -74,11 +74,12 @@ class BLERSSISensor : public sensor::Sensor, public esp32_ble_tracker::ESPBTDevi
|
||||
}
|
||||
break;
|
||||
case MATCH_BY_IBEACON_UUID:
|
||||
if (!device.get_ibeacon().has_value()) {
|
||||
auto maybe_ibeacon = device.get_ibeacon();
|
||||
if (!maybe_ibeacon.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ibeacon = device.get_ibeacon().value();
|
||||
auto ibeacon = *maybe_ibeacon;
|
||||
|
||||
if (this->ibeacon_uuid_ != ibeacon.get_uuid()) {
|
||||
return false;
|
||||
|
||||
@@ -71,16 +71,21 @@ void ClimateIR::setup() {
|
||||
}
|
||||
|
||||
void ClimateIR::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_target_temperature().has_value())
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
if (call.get_fan_mode().has_value())
|
||||
this->fan_mode = *call.get_fan_mode();
|
||||
if (call.get_swing_mode().has_value())
|
||||
this->swing_mode = *call.get_swing_mode();
|
||||
if (call.get_preset().has_value())
|
||||
this->preset = *call.get_preset();
|
||||
auto mode = call.get_mode();
|
||||
if (mode.has_value())
|
||||
this->mode = *mode;
|
||||
auto target_temperature = call.get_target_temperature();
|
||||
if (target_temperature.has_value())
|
||||
this->target_temperature = *target_temperature;
|
||||
auto fan_mode = call.get_fan_mode();
|
||||
if (fan_mode.has_value())
|
||||
this->fan_mode = fan_mode;
|
||||
auto swing_mode = call.get_swing_mode();
|
||||
if (swing_mode.has_value())
|
||||
this->swing_mode = *swing_mode;
|
||||
auto preset = call.get_preset();
|
||||
if (preset.has_value())
|
||||
this->preset = preset;
|
||||
this->transmit_state();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ void LgIrClimate::transmit_state() {
|
||||
if (this->mode == climate::CLIMATE_MODE_OFF) {
|
||||
remote_state |= FAN_AUTO;
|
||||
} else {
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= FAN_MAX;
|
||||
break;
|
||||
|
||||
@@ -23,7 +23,8 @@ class LgIrClimate : public climate_ir::ClimateIR {
|
||||
void control(const climate::ClimateCall &call) override {
|
||||
this->send_swing_cmd_ = call.get_swing_mode().has_value();
|
||||
// swing resets after unit powered off
|
||||
if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
|
||||
auto mode = call.get_mode();
|
||||
if (mode.has_value() && *mode == climate::CLIMATE_MODE_OFF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ void CoolixClimate::transmit_state() {
|
||||
this->fan_mode = climate::CLIMATE_FAN_AUTO;
|
||||
remote_state |= COOLIX_FAN_MODE_AUTO_DRY;
|
||||
} else {
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
remote_state |= COOLIX_FAN_MAX;
|
||||
break;
|
||||
|
||||
@@ -23,7 +23,8 @@ class CoolixClimate : public climate_ir::ClimateIR {
|
||||
void control(const climate::ClimateCall &call) override {
|
||||
send_swing_cmd_ = call.get_swing_mode().has_value();
|
||||
// swing resets after unit powered off
|
||||
if (call.get_mode().has_value() && *call.get_mode() == climate::CLIMATE_MODE_OFF)
|
||||
auto mode = call.get_mode();
|
||||
if (mode.has_value() && *mode == climate::CLIMATE_MODE_OFF)
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
@@ -38,12 +38,15 @@ cover::CoverTraits CopyCover::get_traits() {
|
||||
void CopyCover::control(const cover::CoverCall &call) {
|
||||
auto call2 = source_->make_call();
|
||||
call2.set_stop(call.get_stop());
|
||||
if (call.get_tilt().has_value())
|
||||
call2.set_tilt(*call.get_tilt());
|
||||
if (call.get_position().has_value())
|
||||
call2.set_position(*call.get_position());
|
||||
if (call.get_tilt().has_value())
|
||||
call2.set_tilt(*call.get_tilt());
|
||||
auto tilt = call.get_tilt();
|
||||
if (tilt.has_value())
|
||||
call2.set_tilt(*tilt);
|
||||
auto position = call.get_position();
|
||||
if (position.has_value())
|
||||
call2.set_position(*position);
|
||||
auto tilt2 = call.get_tilt();
|
||||
if (tilt2.has_value())
|
||||
call2.set_tilt(*tilt2);
|
||||
call2.perform();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,14 +45,18 @@ fan::FanTraits CopyFan::get_traits() {
|
||||
|
||||
void CopyFan::control(const fan::FanCall &call) {
|
||||
auto call2 = source_->make_call();
|
||||
if (call.get_state().has_value())
|
||||
call2.set_state(*call.get_state());
|
||||
if (call.get_oscillating().has_value())
|
||||
call2.set_oscillating(*call.get_oscillating());
|
||||
if (call.get_speed().has_value())
|
||||
call2.set_speed(*call.get_speed());
|
||||
if (call.get_direction().has_value())
|
||||
call2.set_direction(*call.get_direction());
|
||||
auto state = call.get_state();
|
||||
if (state.has_value())
|
||||
call2.set_state(*state);
|
||||
auto oscillating = call.get_oscillating();
|
||||
if (oscillating.has_value())
|
||||
call2.set_oscillating(*oscillating);
|
||||
auto speed = call.get_speed();
|
||||
if (speed.has_value())
|
||||
call2.set_speed(*speed);
|
||||
auto direction = call.get_direction();
|
||||
if (direction.has_value())
|
||||
call2.set_direction(*direction);
|
||||
if (call.has_preset_mode())
|
||||
call2.set_preset_mode(call.get_preset_mode());
|
||||
call2.perform();
|
||||
|
||||
@@ -11,8 +11,9 @@ void CopySelect::setup() {
|
||||
|
||||
traits.set_options(source_->traits.get_options());
|
||||
|
||||
if (source_->has_state())
|
||||
this->publish_state(source_->active_index().value());
|
||||
auto idx = this->source_->active_index();
|
||||
if (idx.has_value())
|
||||
this->publish_state(*idx);
|
||||
}
|
||||
|
||||
void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); }
|
||||
|
||||
@@ -37,8 +37,9 @@ void CurrentBasedCover::control(const CoverCall &call) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
auto opt_pos = call.get_position();
|
||||
if (opt_pos.has_value()) {
|
||||
auto pos = *opt_pos;
|
||||
if (fabsf(this->position - pos) < 0.01) {
|
||||
// already at target
|
||||
} else {
|
||||
|
||||
@@ -94,7 +94,7 @@ uint8_t DaikinClimate::operation_mode_() const {
|
||||
|
||||
uint16_t DaikinClimate::fan_speed_() const {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
fan_speed = DAIKIN_FAN_SILENT << 8;
|
||||
break;
|
||||
|
||||
@@ -176,7 +176,7 @@ uint8_t DaikinArcClimate::operation_mode_() {
|
||||
|
||||
uint16_t DaikinArcClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_FAN_1 << 8;
|
||||
break;
|
||||
@@ -485,8 +485,9 @@ bool DaikinArcClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
}
|
||||
|
||||
void DaikinArcClimate::control(const climate::ClimateCall &call) {
|
||||
if (call.get_target_humidity().has_value()) {
|
||||
this->target_humidity = *call.get_target_humidity();
|
||||
auto target_humidity = call.get_target_humidity();
|
||||
if (target_humidity.has_value()) {
|
||||
this->target_humidity = *target_humidity;
|
||||
}
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ uint8_t DaikinBrcClimate::operation_mode_() {
|
||||
|
||||
uint8_t DaikinBrcClimate::fan_speed_swing_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DAIKIN_BRC_FAN_1;
|
||||
break;
|
||||
|
||||
@@ -15,7 +15,7 @@ void DeepSleepComponent::dump_config_platform_() {}
|
||||
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
|
||||
|
||||
void DeepSleepComponent::deep_sleep_() {
|
||||
ESP.deepSleep(*this->sleep_duration_); // NOLINT(readability-static-accessed-through-instance)
|
||||
ESP.deepSleep(this->sleep_duration_.value_or(0)); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
} // namespace deep_sleep
|
||||
|
||||
@@ -64,7 +64,7 @@ uint8_t DelonghiClimate::operation_mode_() {
|
||||
|
||||
uint16_t DelonghiClimate::fan_speed_() {
|
||||
uint16_t fan_speed;
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
fan_speed = DELONGHI_FAN_LOW;
|
||||
break;
|
||||
|
||||
@@ -29,10 +29,11 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
|
||||
protected:
|
||||
void control(const AlarmControlPanelCall &call) override {
|
||||
auto state = call.get_state().value_or(ACP_STATE_DISARMED);
|
||||
auto code = call.get_code();
|
||||
switch (state) {
|
||||
case ACP_STATE_ARMED_AWAY:
|
||||
if (this->get_requires_code_to_arm() && call.get_code().has_value()) {
|
||||
if (call.get_code().value() != "1234") {
|
||||
if (this->get_requires_code_to_arm() && code.has_value()) {
|
||||
if (*code != "1234") {
|
||||
this->status_momentary_error("invalid_code", 5000);
|
||||
return;
|
||||
}
|
||||
@@ -40,8 +41,8 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
|
||||
this->publish_state(ACP_STATE_ARMED_AWAY);
|
||||
break;
|
||||
case ACP_STATE_DISARMED:
|
||||
if (this->get_requires_code() && call.get_code().has_value()) {
|
||||
if (call.get_code().value() != "1234") {
|
||||
if (this->get_requires_code() && code.has_value()) {
|
||||
if (*code != "1234") {
|
||||
this->status_momentary_error("invalid_code", 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,33 +45,31 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
|
||||
protected:
|
||||
void control(const climate::ClimateCall &call) override {
|
||||
if (call.get_mode().has_value()) {
|
||||
this->mode = *call.get_mode();
|
||||
}
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
}
|
||||
if (call.get_target_temperature_low().has_value()) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
}
|
||||
if (call.get_target_temperature_high().has_value()) {
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
}
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
this->set_fan_mode_(*call.get_fan_mode());
|
||||
}
|
||||
if (call.get_swing_mode().has_value()) {
|
||||
this->swing_mode = *call.get_swing_mode();
|
||||
}
|
||||
if (call.has_custom_fan_mode()) {
|
||||
auto mode = call.get_mode();
|
||||
if (mode.has_value())
|
||||
this->mode = *mode;
|
||||
auto target_temperature = call.get_target_temperature();
|
||||
if (target_temperature.has_value())
|
||||
this->target_temperature = *target_temperature;
|
||||
auto target_temperature_low = call.get_target_temperature_low();
|
||||
if (target_temperature_low.has_value())
|
||||
this->target_temperature_low = *target_temperature_low;
|
||||
auto target_temperature_high = call.get_target_temperature_high();
|
||||
if (target_temperature_high.has_value())
|
||||
this->target_temperature_high = *target_temperature_high;
|
||||
auto fan_mode = call.get_fan_mode();
|
||||
if (fan_mode.has_value())
|
||||
this->set_fan_mode_(*fan_mode);
|
||||
auto swing_mode = call.get_swing_mode();
|
||||
if (swing_mode.has_value())
|
||||
this->swing_mode = *swing_mode;
|
||||
if (call.has_custom_fan_mode())
|
||||
this->set_custom_fan_mode_(call.get_custom_fan_mode());
|
||||
}
|
||||
if (call.get_preset().has_value()) {
|
||||
this->set_preset_(*call.get_preset());
|
||||
}
|
||||
if (call.has_custom_preset()) {
|
||||
auto preset = call.get_preset();
|
||||
if (preset.has_value())
|
||||
this->set_preset_(*preset);
|
||||
if (call.has_custom_preset())
|
||||
this->set_custom_preset_(call.get_custom_preset());
|
||||
}
|
||||
this->publish_state();
|
||||
}
|
||||
climate::ClimateTraits traits() override {
|
||||
|
||||
@@ -38,8 +38,9 @@ class DemoCover : public cover::Cover, public Component {
|
||||
|
||||
protected:
|
||||
void control(const cover::CoverCall &call) override {
|
||||
if (call.get_position().has_value()) {
|
||||
float target = *call.get_position();
|
||||
auto pos = call.get_position();
|
||||
if (pos.has_value()) {
|
||||
float target = *pos;
|
||||
this->current_operation =
|
||||
target > this->position ? cover::COVER_OPERATION_OPENING : cover::COVER_OPERATION_CLOSING;
|
||||
|
||||
@@ -49,8 +50,9 @@ class DemoCover : public cover::Cover, public Component {
|
||||
this->publish_state();
|
||||
});
|
||||
}
|
||||
if (call.get_tilt().has_value()) {
|
||||
this->tilt = *call.get_tilt();
|
||||
auto tilt = call.get_tilt();
|
||||
if (tilt.has_value()) {
|
||||
this->tilt = *tilt;
|
||||
}
|
||||
if (call.get_stop()) {
|
||||
this->cancel_timeout("move");
|
||||
|
||||
@@ -47,14 +47,18 @@ class DemoFan : public fan::Fan, public Component {
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_speed().has_value())
|
||||
this->speed = *call.get_speed();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
auto state = call.get_state();
|
||||
if (state.has_value())
|
||||
this->state = *state;
|
||||
auto oscillating = call.get_oscillating();
|
||||
if (oscillating.has_value())
|
||||
this->oscillating = *oscillating;
|
||||
auto speed = call.get_speed();
|
||||
if (speed.has_value())
|
||||
this->speed = *speed;
|
||||
auto direction = call.get_direction();
|
||||
if (direction.has_value())
|
||||
this->direction = *direction;
|
||||
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ namespace demo {
|
||||
class DemoLock : public lock::Lock {
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override {
|
||||
auto state = *call.get_state();
|
||||
this->publish_state(state);
|
||||
auto state = call.get_state();
|
||||
if (state.has_value())
|
||||
this->publish_state(*state);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,12 +26,15 @@ class DemoValve : public valve::Valve {
|
||||
|
||||
protected:
|
||||
void control(const valve::ValveCall &call) override {
|
||||
if (call.get_position().has_value()) {
|
||||
this->position = *call.get_position();
|
||||
auto pos = call.get_position();
|
||||
if (pos.has_value()) {
|
||||
this->position = *pos;
|
||||
this->publish_state();
|
||||
return;
|
||||
} else if (call.get_toggle().has_value()) {
|
||||
if (call.get_toggle().value()) {
|
||||
}
|
||||
auto toggle = call.get_toggle();
|
||||
if (toggle.has_value()) {
|
||||
if (*toggle) {
|
||||
if (this->position == valve::VALVE_OPEN) {
|
||||
this->position = valve::VALVE_CLOSED;
|
||||
this->publish_state();
|
||||
|
||||
@@ -260,6 +260,7 @@ void DFPlayer::loop() {
|
||||
ESP_LOGV(TAG, "Playback finished (USB drive)");
|
||||
this->is_playing_ = false;
|
||||
this->on_finished_playback_callback_.call();
|
||||
break;
|
||||
case 0x3D:
|
||||
ESP_LOGV(TAG, "Playback finished (SD card)");
|
||||
this->is_playing_ = false;
|
||||
|
||||
@@ -30,11 +30,9 @@ class Command {
|
||||
|
||||
class ReadStateCommand : public Command {
|
||||
public:
|
||||
ReadStateCommand() { timeout_ms_ = 500; }
|
||||
uint8_t execute(DfrobotSen0395Component *parent) override;
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint32_t timeout_ms_{500};
|
||||
};
|
||||
|
||||
class PowerCommand : public Command {
|
||||
@@ -99,12 +97,12 @@ class ResetSystemCommand : public Command {
|
||||
|
||||
class SaveCfgCommand : public Command {
|
||||
public:
|
||||
SaveCfgCommand() { cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89"; }
|
||||
SaveCfgCommand() {
|
||||
cmd_ = "saveCfg 0x45670123 0xCDEF89AB 0x956128C6 0xDF54AC89";
|
||||
cmd_duration_ms_ = 3000;
|
||||
timeout_ms_ = 3500;
|
||||
}
|
||||
uint8_t on_message(std::string &message) override;
|
||||
|
||||
protected:
|
||||
uint32_t cmd_duration_ms_{3000};
|
||||
uint32_t timeout_ms_{3500};
|
||||
};
|
||||
|
||||
class LedModeCommand : public Command {
|
||||
|
||||
@@ -661,6 +661,9 @@ void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
|
||||
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
|
||||
|
||||
void Display::set_pages(std::vector<DisplayPage *> pages) {
|
||||
if (pages.empty())
|
||||
return;
|
||||
|
||||
for (auto *page : pages)
|
||||
page->set_parent(this);
|
||||
|
||||
|
||||
@@ -110,9 +110,9 @@ uint8_t DS2484OneWireBus::read8() {
|
||||
}
|
||||
|
||||
uint64_t DS2484OneWireBus::read64() {
|
||||
uint8_t response = 0;
|
||||
uint64_t response = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
response |= (this->read8() << (i * 8));
|
||||
response |= (static_cast<uint64_t>(this->read8()) << (i * 8));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ class EE895Component : public PollingComponent, public i2c::I2CDevice {
|
||||
void write_command_(uint16_t addr, uint16_t reg_cnt);
|
||||
float read_float_();
|
||||
uint16_t calc_crc16_(const uint8_t buf[], uint8_t len);
|
||||
sensor::Sensor *co2_sensor_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
sensor::Sensor *co2_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
sensor::Sensor *pressure_sensor_{nullptr};
|
||||
|
||||
enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE};
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ void Emc2101Component::setup() {
|
||||
config |= EMC2101_DAC_BIT;
|
||||
}
|
||||
if (this->inverted_) {
|
||||
config |= EMC2101_POLARITY_BIT;
|
||||
reg(EMC2101_REGISTER_FAN_CONFIG) |= EMC2101_POLARITY_BIT;
|
||||
}
|
||||
|
||||
if (this->dac_mode_) { // DAC mode configurations
|
||||
|
||||
@@ -28,7 +28,7 @@ uint8_t EmmetiClimate::set_mode_() {
|
||||
}
|
||||
|
||||
uint8_t EmmetiClimate::set_fan_speed_() {
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
return EMMETI_FAN_1;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
|
||||
@@ -37,8 +37,9 @@ void EndstopCover::control(const CoverCall &call) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
auto opt_pos = call.get_position();
|
||||
if (opt_pos.has_value()) {
|
||||
auto pos = *opt_pos;
|
||||
if (pos == this->position) {
|
||||
// already at target
|
||||
} else {
|
||||
|
||||
@@ -209,7 +209,11 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
|
||||
|
||||
esp_gatt_rsp_t response;
|
||||
if (param->read.is_long) {
|
||||
if (this->value_.size() - this->value_read_offset_ < max_offset) {
|
||||
if (this->value_read_offset_ >= this->value_.size()) {
|
||||
response.attr_value.len = 0;
|
||||
response.attr_value.offset = this->value_read_offset_;
|
||||
this->value_read_offset_ = 0;
|
||||
} else if (this->value_.size() - this->value_read_offset_ < max_offset) {
|
||||
// Last message in the chain
|
||||
response.attr_value.len = this->value_.size() - this->value_read_offset_;
|
||||
response.attr_value.offset = this->value_read_offset_;
|
||||
|
||||
@@ -107,7 +107,7 @@ class ESPBTDevice {
|
||||
for (auto &it : this->manufacturer_datas_) {
|
||||
auto res = ESPBLEiBeacon::from_manufacturer_data(it);
|
||||
if (res.has_value())
|
||||
return *res;
|
||||
return res;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -314,6 +314,8 @@ void ESP32ImprovComponent::dump_config() {
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::process_incoming_data_() {
|
||||
if (this->incoming_data_.size() < 3)
|
||||
return;
|
||||
uint8_t length = this->incoming_data_[1];
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
|
||||
@@ -162,7 +162,8 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi
|
||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
// protect from refreshing too often
|
||||
uint32_t now = micros();
|
||||
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
auto rate = this->max_refresh_rate_.value_or(0);
|
||||
if (rate != 0 && (now - this->last_refresh_) < rate) {
|
||||
// try again next loop iteration, so that this change won't get lost
|
||||
this->schedule_show();
|
||||
return;
|
||||
@@ -301,7 +302,7 @@ void ESP32RMTLEDStripLightOutput::dump_config() {
|
||||
" RGB Order: %s\n"
|
||||
" Max refresh rate: %" PRIu32 "\n"
|
||||
" Number of LEDs: %u",
|
||||
rgb_order, *this->max_refresh_rate_, this->num_leds_);
|
||||
rgb_order, this->max_refresh_rate_.value_or(0), this->num_leds_);
|
||||
}
|
||||
|
||||
float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_factory.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -86,7 +86,7 @@ class ESPHomeOTAComponent final : public ota::OTAComponent {
|
||||
|
||||
socket::ListenSocket *server_{nullptr};
|
||||
std::unique_ptr<socket::Socket> client_;
|
||||
std::unique_ptr<ota::OTABackend> backend_;
|
||||
ota::OTABackendPtr backend_;
|
||||
|
||||
uint32_t client_connect_time_{0};
|
||||
uint16_t port_;
|
||||
|
||||
@@ -687,8 +687,6 @@ void EthernetComponent::start_connect_() {
|
||||
this->status_set_warning();
|
||||
}
|
||||
|
||||
bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
esp_netif_ip_info_t ip;
|
||||
esp_netif_get_ip_info(this->eth_netif_, &ip);
|
||||
|
||||
@@ -76,7 +76,7 @@ class EthernetComponent : public Component {
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void on_powerdown() override { powerdown(); }
|
||||
bool is_connected();
|
||||
bool is_connected() { return this->state_ == EthernetComponentState::CONNECTED; }
|
||||
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
|
||||
@@ -66,8 +66,9 @@ void EZOSensor::loop() {
|
||||
|
||||
if (to_run->command_type == EzoCommandType::EZO_SLEEP ||
|
||||
to_run->command_type == EzoCommandType::EZO_I2C) { // Commands with no return data
|
||||
bool update_address = to_run->command_type == EzoCommandType::EZO_I2C;
|
||||
this->commands_.pop_front();
|
||||
if (to_run->command_type == EzoCommandType::EZO_I2C)
|
||||
if (update_address)
|
||||
this->address_ = this->new_address_;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -165,22 +165,23 @@ void EzoPMP::read_command_result_() {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (current_parameter) {
|
||||
case 1:
|
||||
first_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 2:
|
||||
second_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 3:
|
||||
third_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
if (position_in_parameter_buffer < sizeof(first_parameter_buffer) - 1) {
|
||||
switch (current_parameter) {
|
||||
case 1:
|
||||
first_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
first_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 2:
|
||||
second_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
second_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
case 3:
|
||||
third_parameter_buffer[position_in_parameter_buffer] = current_char;
|
||||
third_parameter_buffer[position_in_parameter_buffer + 1] = '\0';
|
||||
break;
|
||||
}
|
||||
position_in_parameter_buffer++;
|
||||
}
|
||||
|
||||
position_in_parameter_buffer++;
|
||||
}
|
||||
|
||||
auto parsed_first_parameter = parse_number<float>(first_parameter_buffer);
|
||||
@@ -404,7 +405,8 @@ void EzoPMP::send_next_command_() {
|
||||
break;
|
||||
|
||||
case EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS: // Run an arbitrary command
|
||||
command_buffer_length = snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_);
|
||||
command_buffer_length =
|
||||
snprintf((char *) command_buffer, sizeof(command_buffer), "%s", this->arbitrary_command_.c_str());
|
||||
ESP_LOGI(TAG, "Sending arbitrary command: %s", (char *) command_buffer);
|
||||
break;
|
||||
|
||||
@@ -541,7 +543,7 @@ void EzoPMP::change_i2c_address(int address) {
|
||||
}
|
||||
|
||||
void EzoPMP::exec_arbitrary_command(const std::basic_string<char> &command) {
|
||||
this->arbitrary_command_ = command.c_str();
|
||||
this->arbitrary_command_ = command;
|
||||
this->queue_command_(EZO_PMP_COMMAND_EXEC_ARBITRARY_COMMAND_ADDRESS, 0, 0, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class EzoPMP : public PollingComponent, public i2c::I2CDevice {
|
||||
bool is_paused_flag_ = false;
|
||||
bool is_dosing_flag_ = false;
|
||||
|
||||
const char *arbitrary_command_{nullptr};
|
||||
std::string arbitrary_command_{};
|
||||
|
||||
void send_next_command_();
|
||||
void read_command_result_();
|
||||
|
||||
@@ -21,12 +21,13 @@ void FastLEDLightOutput::dump_config() {
|
||||
"FastLED light:\n"
|
||||
" Num LEDs: %u\n"
|
||||
" Max refresh rate: %u",
|
||||
this->num_leds_, *this->max_refresh_rate_);
|
||||
this->num_leds_, this->max_refresh_rate_.value_or(0));
|
||||
}
|
||||
void FastLEDLightOutput::write_state(light::LightState *state) {
|
||||
// protect from refreshing too often
|
||||
uint32_t now = micros();
|
||||
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
uint32_t max_rate = this->max_refresh_rate_.value_or(0);
|
||||
if (max_rate != 0 && (now - this->last_refresh_) < max_rate) {
|
||||
// try again next loop iteration, so that this change won't get lost
|
||||
this->schedule_show();
|
||||
return;
|
||||
|
||||
@@ -269,9 +269,12 @@ void FeedbackCover::control(const CoverCall &call) {
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
}
|
||||
}
|
||||
} else if (call.get_position().has_value()) {
|
||||
} else {
|
||||
auto pos_opt = call.get_position();
|
||||
if (!pos_opt.has_value())
|
||||
return;
|
||||
// go to position action
|
||||
auto pos = *call.get_position();
|
||||
auto pos = *pos_opt;
|
||||
if (pos == this->position) {
|
||||
// already at target,
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer) {
|
||||
uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> &data_buffer) {
|
||||
while (this->available())
|
||||
this->read();
|
||||
this->write((uint8_t) (START_CODE >> 8));
|
||||
@@ -372,12 +372,12 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
||||
this->write(this->address_[3]);
|
||||
this->write(COMMAND);
|
||||
|
||||
uint16_t wire_length = p_data_buffer->size() + 2;
|
||||
uint16_t wire_length = data_buffer.size() + 2;
|
||||
this->write((uint8_t) (wire_length >> 8));
|
||||
this->write((uint8_t) (wire_length & 0xFF));
|
||||
|
||||
uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
|
||||
for (auto data : *p_data_buffer) {
|
||||
for (auto data : data_buffer) {
|
||||
this->write(data);
|
||||
sum += data;
|
||||
}
|
||||
@@ -385,7 +385,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
||||
this->write((uint8_t) (sum >> 8));
|
||||
this->write((uint8_t) (sum & 0xFF));
|
||||
|
||||
p_data_buffer->clear();
|
||||
data_buffer.clear();
|
||||
|
||||
uint8_t byte;
|
||||
uint16_t idx = 0, length = 0;
|
||||
@@ -431,9 +431,9 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
||||
length |= byte;
|
||||
break;
|
||||
default:
|
||||
p_data_buffer->push_back(byte);
|
||||
data_buffer.push_back(byte);
|
||||
if ((idx - 8) == length) {
|
||||
switch ((*p_data_buffer)[0]) {
|
||||
switch (data_buffer[0]) {
|
||||
case OK:
|
||||
case NO_FINGER:
|
||||
case IMAGE_FAIL:
|
||||
@@ -453,25 +453,26 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
||||
ESP_LOGE(TAG, "Reader failed to process request");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", (*p_data_buffer)[0]);
|
||||
ESP_LOGE(TAG, "Unknown response received from reader: 0x%.2X", data_buffer[0]);
|
||||
break;
|
||||
}
|
||||
this->last_transfer_ms_ = millis();
|
||||
return (*p_data_buffer)[0];
|
||||
return data_buffer[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ESP_LOGE(TAG, "No response received from reader");
|
||||
(*p_data_buffer)[0] = TIMEOUT;
|
||||
data_buffer.clear();
|
||||
data_buffer.push_back(TIMEOUT);
|
||||
this->last_transfer_ms_ = millis();
|
||||
return TIMEOUT;
|
||||
}
|
||||
|
||||
uint8_t FingerprintGrowComponent::send_command_() {
|
||||
this->sensor_wakeup_();
|
||||
return this->transfer_(&this->data_);
|
||||
return this->transfer_(this->data_);
|
||||
}
|
||||
|
||||
void FingerprintGrowComponent::sensor_wakeup_() {
|
||||
@@ -517,7 +518,7 @@ void FingerprintGrowComponent::sensor_wakeup_() {
|
||||
std::vector<uint8_t> buffer = {VERIFY_PASSWORD, (uint8_t) (this->password_ >> 24), (uint8_t) (this->password_ >> 16),
|
||||
(uint8_t) (this->password_ >> 8), (uint8_t) (this->password_ & 0xFF)};
|
||||
|
||||
if (this->transfer_(&buffer) != OK) {
|
||||
if (this->transfer_(buffer) != OK) {
|
||||
ESP_LOGE(TAG, "Wrong password");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||
bool set_password_();
|
||||
bool get_parameters_();
|
||||
void get_fingerprint_count_();
|
||||
uint8_t transfer_(std::vector<uint8_t> *p_data_buffer);
|
||||
uint8_t transfer_(std::vector<uint8_t> &data_buffer);
|
||||
uint8_t send_command_();
|
||||
void sensor_wakeup_();
|
||||
void sensor_sleep_();
|
||||
@@ -190,7 +190,7 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
|
||||
bool is_sensor_awake_ = false;
|
||||
uint32_t last_transfer_ms_ = 0;
|
||||
uint32_t last_aura_led_control_ = 0;
|
||||
uint16_t last_aura_led_duration_ = 0;
|
||||
uint32_t last_aura_led_duration_ = 0;
|
||||
uint16_t system_identifier_code_ = 0;
|
||||
uint32_t idle_period_to_sleep_ms_ = UINT32_MAX;
|
||||
sensor::Sensor *fingerprint_count_sensor_{nullptr};
|
||||
|
||||
@@ -141,7 +141,7 @@ void FujitsuGeneralClimate::transmit_state() {
|
||||
}
|
||||
|
||||
// Set fan
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_HIGH:
|
||||
SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_HIGH);
|
||||
break;
|
||||
|
||||
@@ -51,7 +51,7 @@ _RESTORING_SCHEMA = cv.Schema(
|
||||
|
||||
def _globals_schema(config: ConfigType) -> ConfigType:
|
||||
"""Select schema based on restore_value setting."""
|
||||
if config.get(CONF_RESTORE_VALUE, False):
|
||||
if cv.boolean(config.get(CONF_RESTORE_VALUE, False)):
|
||||
return _RESTORING_SCHEMA(config)
|
||||
return _NON_RESTORING_SCHEMA(config)
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ AUTO_LOAD = ["sensor"]
|
||||
CODEOWNERS = ["@coogle", "@ximex"]
|
||||
|
||||
gps_ns = cg.esphome_ns.namespace("gps")
|
||||
GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
|
||||
GPS = gps_ns.class_("GPS", cg.PollingComponent, uart.UARTDevice)
|
||||
GPSListener = gps_ns.class_("GPSListener")
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
@@ -180,7 +180,7 @@ uint8_t GreeClimate::operation_mode_() {
|
||||
uint8_t GreeClimate::fan_speed_() {
|
||||
// YX1FF has 4 fan speeds -- we treat low as quiet and turbo as high
|
||||
if (this->model_ == GREE_YX1FF) {
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
return GREE_FAN_1;
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
@@ -195,7 +195,7 @@ uint8_t GreeClimate::fan_speed_() {
|
||||
}
|
||||
}
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
return GREE_FAN_1;
|
||||
case climate::CLIMATE_FAN_MEDIUM:
|
||||
@@ -235,7 +235,7 @@ uint8_t GreeClimate::temperature_() {
|
||||
uint8_t GreeClimate::preset_() {
|
||||
// YX1FF has sleep preset
|
||||
if (this->model_ == GREE_YX1FF) {
|
||||
switch (this->preset.value()) {
|
||||
switch (this->preset.value_or(climate::CLIMATE_PRESET_NONE)) {
|
||||
case climate::CLIMATE_PRESET_NONE:
|
||||
return GREE_PRESET_NONE;
|
||||
case climate::CLIMATE_PRESET_SLEEP:
|
||||
|
||||
@@ -131,7 +131,7 @@ void GroveMotorDriveTB6612FNG::stepper_run(StepperModeTypeT mode, int16_t steps,
|
||||
buffer_[4] = ms_per_step;
|
||||
buffer_[5] = (ms_per_step >> 8);
|
||||
|
||||
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN, buffer_, 1) != i2c::ERROR_OK) {
|
||||
if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN, buffer_, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Run stepper failed!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
|
||||
@@ -893,7 +893,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
} else {
|
||||
this->preset = CLIMATE_PRESET_NONE;
|
||||
}
|
||||
should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value());
|
||||
should_publish = should_publish || (!old_preset.has_value()) ||
|
||||
(old_preset.value_or(CLIMATE_PRESET_NONE) != this->preset.value_or(CLIMATE_PRESET_NONE));
|
||||
}
|
||||
{
|
||||
// Target temperature
|
||||
@@ -936,7 +937,8 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||
this->fan_mode = CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
}
|
||||
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
||||
should_publish = should_publish || (!old_fan_mode.has_value()) ||
|
||||
(old_fan_mode.value_or(CLIMATE_FAN_ON) != this->fan_mode.value_or(CLIMATE_FAN_ON));
|
||||
}
|
||||
// Display status
|
||||
// should be before "Climate mode" because it is changing this->mode
|
||||
@@ -1301,7 +1303,8 @@ void HonClimate::clear_control_messages_queue_() {
|
||||
}
|
||||
|
||||
bool HonClimate::prepare_pending_action() {
|
||||
switch (this->action_request_.value().action) {
|
||||
auto &action_request = this->action_request_.value(); // NOLINT(bugprone-unchecked-optional-access)
|
||||
switch (action_request.action) {
|
||||
case ActionRequest::START_SELF_CLEAN:
|
||||
if (this->control_method_ == HonControlMethod::SET_GROUP_PARAMETERS) {
|
||||
uint8_t control_out_buffer[haier_protocol::MAX_FRAME_SIZE];
|
||||
@@ -1315,12 +1318,12 @@ bool HonClimate::prepare_pending_action() {
|
||||
out_data->ac_power = 1;
|
||||
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
||||
out_data->light_status = 0;
|
||||
this->action_request_.value().message = haier_protocol::HaierMessage(
|
||||
action_request.message = haier_protocol::HaierMessage(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
||||
control_out_buffer, this->real_control_packet_size_);
|
||||
return true;
|
||||
} else if (this->control_method_ == HonControlMethod::SET_SINGLE_PARAMETER) {
|
||||
this->action_request_.value().message =
|
||||
action_request.message =
|
||||
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
|
||||
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
|
||||
(uint8_t) hon_protocol::DataParameters::SELF_CLEANING,
|
||||
@@ -1343,7 +1346,7 @@ bool HonClimate::prepare_pending_action() {
|
||||
out_data->ac_power = 1;
|
||||
out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY;
|
||||
out_data->light_status = 0;
|
||||
this->action_request_.value().message = haier_protocol::HaierMessage(
|
||||
action_request.message = haier_protocol::HaierMessage(
|
||||
haier_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcommandsControl::SET_GROUP_PARAMETERS,
|
||||
control_out_buffer, this->real_control_packet_size_);
|
||||
return true;
|
||||
|
||||
@@ -385,7 +385,7 @@ haier_protocol::HaierMessage Smartair2Climate::get_control_message() {
|
||||
}
|
||||
|
||||
haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||
if (size < sizeof(smartair2_protocol::HaierStatus))
|
||||
if (size != sizeof(smartair2_protocol::HaierStatus))
|
||||
return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE;
|
||||
smartair2_protocol::HaierStatus packet;
|
||||
memcpy(&packet, packet_buffer, size);
|
||||
@@ -402,7 +402,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
||||
} else {
|
||||
this->preset = CLIMATE_PRESET_NONE;
|
||||
}
|
||||
should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value());
|
||||
should_publish = should_publish || (!old_preset.has_value()) ||
|
||||
(old_preset.value_or(CLIMATE_PRESET_NONE) != this->preset.value_or(CLIMATE_PRESET_NONE));
|
||||
}
|
||||
{
|
||||
// Target temperature
|
||||
@@ -446,7 +447,8 @@ haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uin
|
||||
this->fan_mode = CLIMATE_FAN_HIGH;
|
||||
break;
|
||||
}
|
||||
should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value());
|
||||
should_publish = should_publish || (!old_fan_mode.has_value()) ||
|
||||
(old_fan_mode.value_or(CLIMATE_FAN_ON) != this->fan_mode.value_or(CLIMATE_FAN_ON));
|
||||
}
|
||||
// Display status
|
||||
// should be before "Climate mode" because it is changing this->mode
|
||||
|
||||
@@ -49,14 +49,18 @@ void HBridgeFan::dump_config() {
|
||||
}
|
||||
|
||||
void HBridgeFan::control(const fan::FanCall &call) {
|
||||
if (call.get_state().has_value())
|
||||
this->state = *call.get_state();
|
||||
if (call.get_speed().has_value())
|
||||
this->speed = *call.get_speed();
|
||||
if (call.get_oscillating().has_value())
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
auto call_state = call.get_state();
|
||||
if (call_state.has_value())
|
||||
this->state = *call_state;
|
||||
auto call_speed = call.get_speed();
|
||||
if (call_speed.has_value())
|
||||
this->speed = *call_speed;
|
||||
auto call_oscillating = call.get_oscillating();
|
||||
if (call_oscillating.has_value())
|
||||
this->oscillating = *call_oscillating;
|
||||
auto call_direction = call.get_direction();
|
||||
if (call_direction.has_value())
|
||||
this->direction = *call_direction;
|
||||
this->apply_preset_mode_(call);
|
||||
|
||||
this->write_state_();
|
||||
|
||||
@@ -171,9 +171,12 @@ void HE60rCover::control(const CoverCall &call) {
|
||||
} else {
|
||||
this->toggles_needed_++;
|
||||
}
|
||||
} else if (call.get_position().has_value()) {
|
||||
} else {
|
||||
auto pos_opt = call.get_position();
|
||||
if (!pos_opt.has_value())
|
||||
return;
|
||||
// go to position action
|
||||
auto pos = *call.get_position();
|
||||
auto pos = *pos_opt;
|
||||
// are we at the target?
|
||||
if (pos == this->position) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
|
||||
@@ -175,7 +175,7 @@ void HitachiClimate::transmit_state() {
|
||||
|
||||
set_temp_(static_cast<uint8_t>(this->target_temperature));
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
set_fan_(HITACHI_AC344_FAN_LOW);
|
||||
break;
|
||||
|
||||
@@ -176,7 +176,7 @@ void HitachiClimate::transmit_state() {
|
||||
|
||||
set_temp_(static_cast<uint8_t>(this->target_temperature));
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
set_fan_(HITACHI_AC424_FAN_LOW);
|
||||
break;
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
#include "esphome/components/md5/md5.h"
|
||||
#include "esphome/components/watchdog/watchdog.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_rp2040.h"
|
||||
#include "esphome/components/ota/ota_backend_esp_idf.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace http_request {
|
||||
@@ -69,8 +65,7 @@ void OtaHttpRequestComponent::flash() {
|
||||
}
|
||||
}
|
||||
|
||||
void OtaHttpRequestComponent::cleanup_(std::unique_ptr<ota::OTABackend> backend,
|
||||
const std::shared_ptr<HttpContainer> &container) {
|
||||
void OtaHttpRequestComponent::cleanup_(ota::OTABackendPtr backend, const std::shared_ptr<HttpContainer> &container) {
|
||||
if (this->update_started_) {
|
||||
ESP_LOGV(TAG, "Aborting OTA backend");
|
||||
backend->abort();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_factory.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -39,7 +39,7 @@ class OtaHttpRequestComponent final : public ota::OTAComponent, public Parented<
|
||||
void flash();
|
||||
|
||||
protected:
|
||||
void cleanup_(std::unique_ptr<ota::OTABackend> backend, const std::shared_ptr<HttpContainer> &container);
|
||||
void cleanup_(ota::OTABackendPtr backend, const std::shared_ptr<HttpContainer> &container);
|
||||
uint8_t do_ota_();
|
||||
std::string get_url_with_auth_(const std::string &url);
|
||||
bool http_get_md5_();
|
||||
|
||||
@@ -11,17 +11,18 @@ static const char *const TAG = "audio";
|
||||
|
||||
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
media_player::MediaPlayerState play_state = media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
if (call.get_announcement().has_value()) {
|
||||
play_state = call.get_announcement().value() ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||
: media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
auto announcement = call.get_announcement();
|
||||
if (announcement.has_value()) {
|
||||
play_state = *announcement ? media_player::MEDIA_PLAYER_STATE_ANNOUNCING : media_player::MEDIA_PLAYER_STATE_PLAYING;
|
||||
}
|
||||
if (call.get_media_url().has_value()) {
|
||||
this->current_url_ = call.get_media_url();
|
||||
auto media_url = call.get_media_url();
|
||||
if (media_url.has_value()) {
|
||||
this->current_url_ = media_url;
|
||||
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
|
||||
if (this->audio_->isRunning()) {
|
||||
this->audio_->stopSong();
|
||||
}
|
||||
this->audio_->connecttohost(this->current_url_.value().c_str());
|
||||
this->audio_->connecttohost(media_url->c_str());
|
||||
this->state = play_state;
|
||||
} else {
|
||||
this->start();
|
||||
@@ -32,13 +33,15 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
this->is_announcement_ = true;
|
||||
}
|
||||
|
||||
if (call.get_volume().has_value()) {
|
||||
this->volume = call.get_volume().value();
|
||||
auto vol = call.get_volume();
|
||||
if (vol.has_value()) {
|
||||
this->volume = *vol;
|
||||
this->set_volume_(volume);
|
||||
this->unmute_();
|
||||
}
|
||||
if (call.get_command().has_value()) {
|
||||
switch (call.get_command().value()) {
|
||||
auto cmd = call.get_command();
|
||||
if (cmd.has_value()) {
|
||||
switch (*cmd) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_MUTE:
|
||||
this->mute_();
|
||||
break;
|
||||
@@ -67,7 +70,7 @@ void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
|
||||
if (this->i2s_state_ != I2S_STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
switch (call.get_command().value()) {
|
||||
switch (*cmd) {
|
||||
case media_player::MEDIA_PLAYER_COMMAND_PLAY:
|
||||
if (!this->audio_->isRunning())
|
||||
this->audio_->pauseResume();
|
||||
|
||||
@@ -90,8 +90,9 @@ void Infrared::control(const InfraredCall &call) {
|
||||
auto *transmit_data = transmit_call.get_data();
|
||||
|
||||
// Set carrier frequency
|
||||
if (call.get_carrier_frequency().has_value()) {
|
||||
transmit_data->set_carrier_frequency(call.get_carrier_frequency().value());
|
||||
auto freq = call.get_carrier_frequency();
|
||||
if (freq.has_value()) {
|
||||
transmit_data->set_carrier_frequency(*freq);
|
||||
}
|
||||
|
||||
// Set timings based on format
|
||||
|
||||
@@ -63,16 +63,26 @@ void Inkplate::initialize_() {
|
||||
if (buffer_size == 0)
|
||||
return;
|
||||
|
||||
if (this->partial_buffer_ != nullptr)
|
||||
if (this->partial_buffer_ != nullptr) {
|
||||
allocator.deallocate(this->partial_buffer_, buffer_size);
|
||||
if (this->partial_buffer_2_ != nullptr)
|
||||
this->partial_buffer_ = nullptr;
|
||||
}
|
||||
if (this->partial_buffer_2_ != nullptr) {
|
||||
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
|
||||
if (this->buffer_ != nullptr)
|
||||
this->partial_buffer_2_ = nullptr;
|
||||
}
|
||||
if (this->buffer_ != nullptr) {
|
||||
allocator.deallocate(this->buffer_, buffer_size);
|
||||
if (this->glut_ != nullptr)
|
||||
this->buffer_ = nullptr;
|
||||
}
|
||||
if (this->glut_ != nullptr) {
|
||||
allocator32.deallocate(this->glut_, 256 * 9);
|
||||
if (this->glut2_ != nullptr)
|
||||
this->glut_ = nullptr;
|
||||
}
|
||||
if (this->glut2_ != nullptr) {
|
||||
allocator32.deallocate(this->glut2_, 256 * 9);
|
||||
this->glut2_ = nullptr;
|
||||
}
|
||||
|
||||
this->buffer_ = allocator.allocate(buffer_size);
|
||||
if (this->buffer_ == nullptr) {
|
||||
|
||||
@@ -32,6 +32,7 @@ class IntegrationSensor : public sensor::Sensor, public Component {
|
||||
void set_method(IntegrationMethod method) { method_ = method; }
|
||||
void set_restore(bool restore) { restore_ = restore; }
|
||||
void reset() { this->publish_and_save_(0.0f); }
|
||||
void set_value(float value) { this->publish_and_save_(value); }
|
||||
|
||||
protected:
|
||||
void process_sensor_value_(float value);
|
||||
@@ -71,14 +72,16 @@ class IntegrationSensor : public sensor::Sensor, public Component {
|
||||
float last_value_{0.0f};
|
||||
};
|
||||
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...> {
|
||||
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<IntegrationSensor> {
|
||||
public:
|
||||
explicit ResetAction(IntegrationSensor *parent) : parent_(parent) {}
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->reset(); }
|
||||
};
|
||||
|
||||
protected:
|
||||
IntegrationSensor *parent_;
|
||||
template<typename... Ts> class SetValueAction : public Action<Ts...>, public Parented<IntegrationSensor> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, value)
|
||||
|
||||
void play(const Ts &...x) override { this->parent_->set_value(this->value_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace integration
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_RESTORE,
|
||||
CONF_SENSOR,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
|
||||
@@ -17,6 +18,7 @@ IntegrationSensor = integration_ns.class_(
|
||||
"IntegrationSensor", sensor.Sensor, cg.Component
|
||||
)
|
||||
ResetAction = integration_ns.class_("ResetAction", automation.Action)
|
||||
SetValueAction = integration_ns.class_("SetValueAction", automation.Action)
|
||||
|
||||
IntegrationSensorTime = integration_ns.enum("IntegrationSensorTime")
|
||||
INTEGRATION_TIMES = {
|
||||
@@ -111,5 +113,24 @@ async def to_code(config):
|
||||
),
|
||||
)
|
||||
async def sensor_integration_reset_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"sensor.integration.set_value",
|
||||
SetValueAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(IntegrationSensor),
|
||||
cv.Required(CONF_VALUE): cv.templatable(cv.float_),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def sensor_integration_set_value_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, float)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@@ -136,7 +136,7 @@ void KamstrupKMPComponent::read_command_(uint16_t command) {
|
||||
int timeout = 250; // ms
|
||||
|
||||
// Read the data from the UART
|
||||
while (timeout > 0) {
|
||||
while (timeout > 0 && buffer_len < static_cast<int>(sizeof(buffer))) {
|
||||
if (this->available()) {
|
||||
data = this->read();
|
||||
if (data > -1) {
|
||||
@@ -246,7 +246,7 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_
|
||||
}
|
||||
|
||||
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
|
||||
const char *unit = UNITS[unit_idx];
|
||||
const char *unit = unit_idx < sizeof(UNITS) / sizeof(UNITS[0]) ? UNITS[unit_idx] : "";
|
||||
|
||||
// Standard sensors
|
||||
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
|
||||
|
||||
@@ -99,7 +99,7 @@ void HOT LCDDisplay::display() {
|
||||
this->send(this->buffer_[this->columns_ * 2 + i], true);
|
||||
}
|
||||
|
||||
if (this->rows_ >= 1) {
|
||||
if (this->rows_ >= 2) {
|
||||
this->command_(LCD_DISPLAY_COMMAND_SET_DDRAM_ADDR | 0x40);
|
||||
|
||||
for (uint8_t i = 0; i < this->columns_; i++)
|
||||
|
||||
@@ -460,6 +460,10 @@ 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]);
|
||||
if (len < static_cast<int>(index + 1 + sizeof(range) + elements * sizeof(this->gate_energy_[0]))) {
|
||||
ESP_LOGW(TAG, "Energy frame too short: %d bytes", len);
|
||||
return;
|
||||
}
|
||||
this->set_presence_(buffer[index]);
|
||||
index++;
|
||||
memcpy(&range, &buffer[index], sizeof(range));
|
||||
@@ -471,8 +475,11 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
||||
}
|
||||
|
||||
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++;
|
||||
this->update_radar_data(gate_energy_, this->sample_number_counter);
|
||||
this->sample_number_counter++;
|
||||
if (this->sample_number_counter >= CALIBRATE_SAMPLES) {
|
||||
this->sample_number_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
@@ -503,22 +510,20 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
char *endptr{nullptr};
|
||||
char outbuf[bufsize]{0};
|
||||
while (true) {
|
||||
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
||||
if (pos >= 2 && inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
||||
this->set_presence_(false);
|
||||
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
||||
} else if (pos >= 1 && inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
||||
this->set_presence_(true);
|
||||
}
|
||||
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
|
||||
if (index < bufsize - 1) {
|
||||
outbuf[index++] = inbuf[pos];
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
if (pos < len - 1) {
|
||||
pos++;
|
||||
} else {
|
||||
if (pos < len - 1) {
|
||||
pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
outbuf[index] = '\0';
|
||||
|
||||
@@ -56,7 +56,8 @@ optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
|
||||
esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num,
|
||||
uint8_t channel, uint8_t &bit_depth, float frequency) {
|
||||
bit_depth = *ledc_bit_depth_for_frequency(frequency);
|
||||
auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency);
|
||||
bit_depth = bit_depth_opt.value_or(0);
|
||||
if (bit_depth < 1) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@ bool LwTx::lwtx_free() { return !this->tx_msg_active; }
|
||||
Send a LightwaveRF message (10 nibbles in bytes)
|
||||
**/
|
||||
void LwTx::lwtx_send(const std::vector<uint8_t> &msg) {
|
||||
if (msg.size() < TX_MSGLEN) {
|
||||
ESP_LOGW("lightwaverf.sensor", "Message too short: %zu < %u", msg.size(), static_cast<unsigned>(TX_MSGLEN));
|
||||
return;
|
||||
}
|
||||
if (this->tx_translate) {
|
||||
for (uint8_t i = 0; i < TX_MSGLEN; i++) {
|
||||
this->tx_buf[i] = TX_NIBBLE[msg[i] & 0xF];
|
||||
|
||||
@@ -421,7 +421,7 @@ void LvglComponent::write_random_() {
|
||||
col = col / this->draw_rounding * this->draw_rounding;
|
||||
auto row = random_uint32() % this->disp_drv_.ver_res;
|
||||
row = row / this->draw_rounding * this->draw_rounding;
|
||||
auto size = (random_uint32() % 32) / this->draw_rounding * this->draw_rounding - 1;
|
||||
auto size = ((random_uint32() % 32) / this->draw_rounding + 2) * this->draw_rounding - 1;
|
||||
lv_area_t area;
|
||||
area.x1 = col;
|
||||
area.y1 = row;
|
||||
|
||||
@@ -19,8 +19,9 @@ void Mcp4461Component::setup() {
|
||||
// save WP/WL status
|
||||
this->update_write_protection_status_();
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (this->reg_[i].initial_value.has_value()) {
|
||||
uint16_t initial_state = static_cast<uint16_t>(*this->reg_[i].initial_value * 256.0f);
|
||||
auto init_val = this->reg_[i].initial_value;
|
||||
if (init_val.has_value()) {
|
||||
uint16_t initial_state = static_cast<uint16_t>(*init_val * 256.0f);
|
||||
this->write_wiper_level_(i, initial_state);
|
||||
}
|
||||
if (this->reg_[i].enabled) {
|
||||
|
||||
@@ -67,11 +67,13 @@ class MediaSource {
|
||||
/// @brief Start playing the given URI
|
||||
/// Sources should validate the URI and state, returning false if the source is busy.
|
||||
/// The orchestrator is responsible for stopping active sources before starting a new one.
|
||||
/// @note Must only be called from the main loop.
|
||||
/// @param uri URI to play; e.g., "http://stream_url"
|
||||
/// @return true if playback started successfully, false otherwise
|
||||
virtual bool play_uri(const std::string &uri) = 0;
|
||||
|
||||
/// @brief Handle playback commands (pause, stop, next, etc.)
|
||||
/// @brief Handle playback commands; e.g., pause, stop, next, etc.
|
||||
/// @note Must only be called from the main loop.
|
||||
/// @param command Command to execute
|
||||
virtual void handle_command(MediaSourceCommand command) = 0;
|
||||
|
||||
@@ -81,7 +83,8 @@ class MediaSource {
|
||||
|
||||
// === State Access ===
|
||||
|
||||
/// @brief Get current playback state (must only be called from the main loop)
|
||||
/// @brief Get current playback state
|
||||
/// @note Must only be called from the main loop.
|
||||
/// @return Current state of this source
|
||||
MediaSourceState get_state() const { return this->state_; }
|
||||
|
||||
@@ -136,9 +139,10 @@ class MediaSource {
|
||||
virtual void notify_audio_played(uint32_t frames, int64_t timestamp) {}
|
||||
|
||||
protected:
|
||||
/// @brief Update state and notify listener (must only be called from the main loop)
|
||||
/// @brief Update state and notify listener
|
||||
/// This is the only way to change state_, ensuring listener notifications always fire.
|
||||
/// Sources running FreeRTOS tasks should signal via event groups and call this from loop().
|
||||
/// @note Must only be called from the main loop.
|
||||
/// @param state New state to set
|
||||
void set_state_(MediaSourceState state) {
|
||||
if (this->state_ != state) {
|
||||
|
||||
@@ -56,20 +56,25 @@ void AirConditioner::on_status_change() {
|
||||
|
||||
void AirConditioner::control(const ClimateCall &call) {
|
||||
dudanov::midea::ac::Control ctrl{};
|
||||
if (call.get_target_temperature().has_value())
|
||||
ctrl.targetTemp = call.get_target_temperature().value();
|
||||
if (call.get_swing_mode().has_value())
|
||||
ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value());
|
||||
if (call.get_mode().has_value())
|
||||
ctrl.mode = Converters::to_midea_mode(call.get_mode().value());
|
||||
if (call.get_preset().has_value()) {
|
||||
ctrl.preset = Converters::to_midea_preset(call.get_preset().value());
|
||||
auto target_temp_val = call.get_target_temperature();
|
||||
if (target_temp_val.has_value())
|
||||
ctrl.targetTemp = *target_temp_val;
|
||||
auto swing_mode_val = call.get_swing_mode();
|
||||
if (swing_mode_val.has_value())
|
||||
ctrl.swingMode = Converters::to_midea_swing_mode(*swing_mode_val);
|
||||
auto mode_val = call.get_mode();
|
||||
if (mode_val.has_value())
|
||||
ctrl.mode = Converters::to_midea_mode(*mode_val);
|
||||
auto preset_val = call.get_preset();
|
||||
if (preset_val.has_value()) {
|
||||
ctrl.preset = Converters::to_midea_preset(*preset_val);
|
||||
} else if (call.has_custom_preset()) {
|
||||
// get_custom_preset() returns StringRef pointing to null-terminated string literals from codegen
|
||||
ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().c_str());
|
||||
}
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value());
|
||||
auto fan_mode_val = call.get_fan_mode();
|
||||
if (fan_mode_val.has_value()) {
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(*fan_mode_val);
|
||||
} else if (call.has_custom_fan_mode()) {
|
||||
// get_custom_fan_mode() returns StringRef pointing to null-terminated string literals from codegen
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().c_str());
|
||||
|
||||
@@ -114,15 +114,20 @@ void MideaIR::control(const climate::ClimateCall &call) {
|
||||
if (call.get_mode() == climate::CLIMATE_MODE_OFF) {
|
||||
this->swing_mode = climate::CLIMATE_SWING_OFF;
|
||||
this->preset = climate::CLIMATE_PRESET_NONE;
|
||||
} else if (call.get_swing_mode().has_value() && ((*call.get_swing_mode() == climate::CLIMATE_SWING_OFF &&
|
||||
this->swing_mode == climate::CLIMATE_SWING_VERTICAL) ||
|
||||
(*call.get_swing_mode() == climate::CLIMATE_SWING_VERTICAL &&
|
||||
this->swing_mode == climate::CLIMATE_SWING_OFF))) {
|
||||
this->swing_ = true;
|
||||
} else if (call.get_preset().has_value() &&
|
||||
((*call.get_preset() == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) ||
|
||||
(*call.get_preset() == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) {
|
||||
this->boost_ = true;
|
||||
} else {
|
||||
auto swing = call.get_swing_mode();
|
||||
if (swing.has_value() &&
|
||||
((*swing == climate::CLIMATE_SWING_OFF && this->swing_mode == climate::CLIMATE_SWING_VERTICAL) ||
|
||||
(*swing == climate::CLIMATE_SWING_VERTICAL && this->swing_mode == climate::CLIMATE_SWING_OFF))) {
|
||||
this->swing_ = true;
|
||||
} else {
|
||||
auto preset = call.get_preset();
|
||||
if (preset.has_value() &&
|
||||
((*preset == climate::CLIMATE_PRESET_NONE && this->preset == climate::CLIMATE_PRESET_BOOST) ||
|
||||
(*preset == climate::CLIMATE_PRESET_BOOST && this->preset == climate::CLIMATE_PRESET_NONE))) {
|
||||
this->boost_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
climate_ir::ClimateIR::control(call);
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ void MitsubishiClimate::transmit_state() {
|
||||
// For 5Level: Low = 1, Middle = 2, Medium = 3, High = 4
|
||||
// For 4Level + Quiet: Low = 1, Middle = 2, Medium = 3, High = 4, Quiet = 5
|
||||
|
||||
switch (this->fan_mode.value()) {
|
||||
switch (this->fan_mode.value_or(climate::CLIMATE_FAN_ON)) {
|
||||
case climate::CLIMATE_FAN_LOW:
|
||||
remote_state[9] = 1;
|
||||
break;
|
||||
@@ -209,7 +209,8 @@ void MitsubishiClimate::transmit_state() {
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "fan: %02x state: %02x", this->fan_mode.value(), remote_state[9]);
|
||||
ESP_LOGD(TAG, "fan: %02x state: %02x", static_cast<uint8_t>(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)),
|
||||
remote_state[9]);
|
||||
|
||||
// Vertical Vane
|
||||
switch (this->swing_mode) {
|
||||
@@ -227,7 +228,7 @@ void MitsubishiClimate::transmit_state() {
|
||||
ESP_LOGD(TAG, "default_vertical_direction_: %02X", this->default_vertical_direction_);
|
||||
|
||||
// Special modes
|
||||
switch (this->preset.value()) {
|
||||
switch (this->preset.value_or(climate::CLIMATE_PRESET_NONE)) {
|
||||
case climate::CLIMATE_PRESET_ECO:
|
||||
remote_state[6] = MITSUBISHI_MODE_COOL | MITSUBISHI_OTHERWISE;
|
||||
remote_state[8] = (remote_state[8] & ~7) | MITSUBISHI_MODE_A_COOL;
|
||||
|
||||
@@ -438,24 +438,14 @@ void MixerSpeaker::loop() {
|
||||
// Handle pending start request
|
||||
if (event_group_bits & MIXER_TASK_COMMAND_START) {
|
||||
// Only start the task if it's fully stopped and cleaned up
|
||||
if (!this->status_has_error() && (this->task_handle_ == nullptr) && (this->task_stack_buffer_ == nullptr)) {
|
||||
esp_err_t err = this->start_task_();
|
||||
switch (err) {
|
||||
case ESP_OK:
|
||||
xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_START);
|
||||
break;
|
||||
case ESP_ERR_NO_MEM:
|
||||
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
|
||||
this->status_momentary_error("memory-failure", 1000);
|
||||
return;
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
|
||||
this->status_momentary_error("task-failure", 1000);
|
||||
return;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
|
||||
this->status_momentary_error("failure", 1000);
|
||||
return;
|
||||
if (!this->status_has_error() && !this->task_.is_created()) {
|
||||
if (this->task_.create(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this, MIXER_TASK_PRIORITY,
|
||||
this->task_stack_in_psram_)) {
|
||||
xEventGroupClearBits(this->event_group_, MIXER_TASK_COMMAND_START);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to start; retrying in 1 second");
|
||||
this->status_momentary_error("failure", 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,13 +468,12 @@ void MixerSpeaker::loop() {
|
||||
xEventGroupClearBits(this->event_group_, MIXER_TASK_STATE_STOPPING);
|
||||
}
|
||||
if (event_group_bits & MIXER_TASK_STATE_STOPPED) {
|
||||
if (this->delete_task_() == ESP_OK) {
|
||||
ESP_LOGD(TAG, "Stopped");
|
||||
xEventGroupClearBits(this->event_group_, MIXER_TASK_ALL_BITS);
|
||||
}
|
||||
this->task_.deallocate();
|
||||
ESP_LOGD(TAG, "Stopped");
|
||||
xEventGroupClearBits(this->event_group_, MIXER_TASK_ALL_BITS);
|
||||
}
|
||||
|
||||
if (this->task_handle_ != nullptr) {
|
||||
if (this->task_.is_created()) {
|
||||
// If the mixer task is running, check if all source speakers are stopped
|
||||
|
||||
bool all_stopped = true;
|
||||
@@ -497,7 +486,7 @@ void MixerSpeaker::loop() {
|
||||
// Send stop command signal to the mixer task since no source speakers are active
|
||||
xEventGroupSetBits(this->event_group_, MIXER_TASK_COMMAND_STOP);
|
||||
}
|
||||
} else if (this->task_stack_buffer_ == nullptr) {
|
||||
} else {
|
||||
// Task is fully stopped and cleaned up, check if we can disable loop
|
||||
event_group_bits = xEventGroupGetBits(this->event_group_);
|
||||
if (event_group_bits == 0) {
|
||||
@@ -538,60 +527,6 @@ esp_err_t MixerSpeaker::start(audio::AudioStreamInfo &stream_info) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t MixerSpeaker::start_task_() {
|
||||
if (this->task_stack_buffer_ == nullptr) {
|
||||
if (this->task_stack_in_psram_) {
|
||||
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
|
||||
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
|
||||
} else {
|
||||
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
|
||||
this->task_stack_buffer_ = stack_allocator.allocate(TASK_STACK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->task_stack_buffer_ == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
if (this->task_handle_ == nullptr) {
|
||||
this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this,
|
||||
MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_);
|
||||
}
|
||||
|
||||
if (this->task_handle_ == nullptr) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t MixerSpeaker::delete_task_() {
|
||||
if (this->task_handle_ != nullptr) {
|
||||
// Delete the task
|
||||
vTaskDelete(this->task_handle_);
|
||||
this->task_handle_ = nullptr;
|
||||
}
|
||||
|
||||
if ((this->task_handle_ == nullptr) && (this->task_stack_buffer_ != nullptr)) {
|
||||
// Deallocate the task stack buffer
|
||||
if (this->task_stack_in_psram_) {
|
||||
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_EXTERNAL);
|
||||
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
|
||||
} else {
|
||||
RAMAllocator<StackType_t> stack_allocator(RAMAllocator<StackType_t>::ALLOC_INTERNAL);
|
||||
stack_allocator.deallocate(this->task_stack_buffer_, TASK_STACK_SIZE);
|
||||
}
|
||||
|
||||
this->task_stack_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
if ((this->task_handle_ != nullptr) || (this->task_stack_buffer_ != nullptr)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void MixerSpeaker::copy_frames(const int16_t *input_buffer, audio::AudioStreamInfo input_stream_info,
|
||||
int16_t *output_buffer, audio::AudioStreamInfo output_stream_info,
|
||||
uint32_t frames_to_transfer) {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/static_task.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#include <atomic>
|
||||
@@ -143,8 +143,6 @@ class MixerSpeaker : public Component {
|
||||
/// @param stream_info The calling source speaker's audio stream information
|
||||
/// @return ESP_ERR_NOT_SUPPORTED if the incoming stream is incompatible due to unsupported bits per sample
|
||||
/// ESP_ERR_INVALID_ARG if the incoming stream is incompatible to be mixed with the other input audio stream
|
||||
/// ESP_ERR_NO_MEM if there isn't enough memory for the task's stack
|
||||
/// ESP_ERR_INVALID_STATE if the task fails to start
|
||||
/// ESP_OK if the incoming stream is compatible and the mixer task starts
|
||||
esp_err_t start(audio::AudioStreamInfo &stream_info);
|
||||
|
||||
@@ -188,16 +186,6 @@ class MixerSpeaker : public Component {
|
||||
|
||||
static void audio_mixer_task(void *params);
|
||||
|
||||
/// @brief Starts the mixer task after allocating memory for the task stack.
|
||||
/// @return ESP_ERR_NO_MEM if there isn't enough memory for the task's stack
|
||||
/// ESP_ERR_INVALID_STATE if the task didn't start
|
||||
/// ESP_OK if successful
|
||||
esp_err_t start_task_();
|
||||
|
||||
/// @brief If the task is stopped, it sets the task handle to the nullptr and deallocates its stack
|
||||
/// @return ESP_OK if the task was stopped, ESP_ERR_INVALID_STATE otherwise.
|
||||
esp_err_t delete_task_();
|
||||
|
||||
EventGroupHandle_t event_group_{nullptr};
|
||||
|
||||
FixedVector<SourceSpeaker *> source_speakers_;
|
||||
@@ -207,9 +195,7 @@ class MixerSpeaker : public Component {
|
||||
bool queue_mode_;
|
||||
bool task_stack_in_psram_{false};
|
||||
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
StaticTask_t task_stack_;
|
||||
StackType_t *task_stack_buffer_{nullptr};
|
||||
StaticTask task_;
|
||||
|
||||
optional<audio::AudioStreamInfo> audio_stream_info_;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ void ModbusSelect::control(size_t index) {
|
||||
// Transform func requires string parameter for backward compatibility
|
||||
auto val = (*this->write_transform_func_)(this, std::string(option), *mapval, data);
|
||||
if (val.has_value()) {
|
||||
mapval = *val;
|
||||
mapval = val;
|
||||
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control");
|
||||
|
||||
@@ -108,7 +108,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
||||
}
|
||||
|
||||
// Get temperature of sensor
|
||||
uint8_t temp_in_c = this->parse_temperature_(mopeka_data);
|
||||
int8_t temp_in_c = this->parse_temperature_(mopeka_data);
|
||||
if (this->temperature_ != nullptr) {
|
||||
this->temperature_->publish_state(temp_in_c);
|
||||
}
|
||||
@@ -223,12 +223,12 @@ uint8_t MopekaStdCheck::parse_battery_level_(const mopeka_std_package *message)
|
||||
return (uint8_t) percent;
|
||||
}
|
||||
|
||||
uint8_t MopekaStdCheck::parse_temperature_(const mopeka_std_package *message) {
|
||||
int8_t MopekaStdCheck::parse_temperature_(const mopeka_std_package *message) {
|
||||
uint8_t tmp = message->raw_temp;
|
||||
if (tmp == 0x0) {
|
||||
return -40;
|
||||
} else {
|
||||
return (uint8_t) ((tmp - 25.0f) * 1.776964f);
|
||||
return static_cast<int8_t>((tmp - 25.0f) * 1.776964f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user