mirror of
https://github.com/esphome/esphome.git
synced 2026-06-25 20:50:34 +00:00
Compare commits
10 Commits
split-hal-
...
2026.4.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fda5f41b2 | ||
|
|
197d4dac8e | ||
|
|
2d7f9dc48d | ||
|
|
be84e6c9f4 | ||
|
|
0418f2138a | ||
|
|
d9c22d6b56 | ||
|
|
60a94fd109 | ||
|
|
9371ec319a | ||
|
|
ce466c6b60 | ||
|
|
a460f5343c |
@@ -1 +1 @@
|
||||
1b1ce6324c50c4595703c7df0a8a479b4fe84b71ff1a8793cce1a16f17a33324
|
||||
10c432ae818f9ed7fd4a0176a04467b1f2634363f5ec985045a6d72747f60b90
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"--privileged",
|
||||
"-e",
|
||||
"GIT_EDITOR=code --wait"
|
||||
// uncomment and edit the path in order to pass through local USB serial to the container
|
||||
// uncomment and edit the path in order to pass though local USB serial to the conatiner
|
||||
// , "--device=/dev/ttyACM0"
|
||||
],
|
||||
"appPort": 6052,
|
||||
|
||||
4
.github/actions/build-image/action.yaml
vendored
4
.github/actions/build-image/action.yaml
vendored
@@ -47,7 +47,7 @@ runs:
|
||||
|
||||
- name: Build and push to ghcr by digest
|
||||
id: build-ghcr
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
@@ -73,7 +73,7 @@ runs:
|
||||
|
||||
- name: Build and push to dockerhub by digest
|
||||
id: build-dockerhub
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
env:
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
2
.github/actions/restore-python/action.yml
vendored
2
.github/actions/restore-python/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
|
||||
1
.github/scripts/auto-label-pr/constants.js
vendored
1
.github/scripts/auto-label-pr/constants.js
vendored
@@ -4,7 +4,6 @@ module.exports = {
|
||||
CODEOWNERS_MARKER: '<!-- codeowners-request -->',
|
||||
TOO_BIG_MARKER: '<!-- too-big-request -->',
|
||||
DEPRECATED_COMPONENT_MARKER: '<!-- deprecated-component-request -->',
|
||||
ORG_FORK_MARKER: '<!-- maintainer-access-warning -->',
|
||||
|
||||
MANAGED_LABELS: [
|
||||
'new-component',
|
||||
|
||||
19
.github/scripts/auto-label-pr/detectors.js
vendored
19
.github/scripts/auto-label-pr/detectors.js
vendored
@@ -281,24 +281,6 @@ async function detectDeprecatedComponents(github, context, changedFiles) {
|
||||
return { labels, deprecatedInfo };
|
||||
}
|
||||
|
||||
// Strategy: Detect when maintainers cannot modify the PR branch
|
||||
function detectMaintainerAccess(context) {
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Only relevant for cross-repo PRs (forks)
|
||||
if (!pr.head.repo || pr.head.repo.full_name === pr.base.repo.full_name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pr.maintainer_can_modify) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isOrgFork = pr.head.repo.owner.type === 'Organization';
|
||||
console.log(`Maintainer cannot modify PR branch (${isOrgFork ? 'org fork: ' + pr.head.repo.owner.login : 'user disabled'})`);
|
||||
return { isOrgFork, orgName: pr.head.repo.owner.login };
|
||||
}
|
||||
|
||||
// Strategy: Requirements detection
|
||||
async function detectRequirements(allLabels, prFiles, context) {
|
||||
const labels = new Set();
|
||||
@@ -347,6 +329,5 @@ module.exports = {
|
||||
detectTests,
|
||||
detectPRTemplateCheckboxes,
|
||||
detectDeprecatedComponents,
|
||||
detectMaintainerAccess,
|
||||
detectRequirements
|
||||
};
|
||||
|
||||
16
.github/scripts/auto-label-pr/index.js
vendored
16
.github/scripts/auto-label-pr/index.js
vendored
@@ -12,10 +12,9 @@ const {
|
||||
detectTests,
|
||||
detectPRTemplateCheckboxes,
|
||||
detectDeprecatedComponents,
|
||||
detectMaintainerAccess,
|
||||
detectRequirements
|
||||
} = require('./detectors');
|
||||
const { handleReviews, handleMaintainerAccessComment } = require('./reviews');
|
||||
const { handleReviews } = require('./reviews');
|
||||
const { applyLabels, removeOldLabels } = require('./labels');
|
||||
|
||||
// Fetch API data
|
||||
@@ -115,8 +114,7 @@ module.exports = async ({ github, context }) => {
|
||||
codeOwnerLabels,
|
||||
testLabels,
|
||||
checkboxLabels,
|
||||
deprecatedResult,
|
||||
maintainerAccess
|
||||
deprecatedResult
|
||||
] = await Promise.all([
|
||||
detectMergeBranch(context),
|
||||
detectComponentPlatforms(changedFiles, apiData),
|
||||
@@ -129,8 +127,7 @@ module.exports = async ({ github, context }) => {
|
||||
detectCodeOwner(github, context, changedFiles),
|
||||
detectTests(changedFiles),
|
||||
detectPRTemplateCheckboxes(context),
|
||||
detectDeprecatedComponents(github, context, changedFiles),
|
||||
detectMaintainerAccess(context)
|
||||
detectDeprecatedComponents(github, context, changedFiles)
|
||||
]);
|
||||
|
||||
// Extract deprecated component info
|
||||
@@ -180,11 +177,8 @@ module.exports = async ({ github, context }) => {
|
||||
|
||||
console.log('Computed labels:', finalLabels.join(', '));
|
||||
|
||||
// Handle reviews and org fork comment
|
||||
await Promise.all([
|
||||
handleReviews(github, context, finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, MAX_LABELS, TOO_BIG_THRESHOLD),
|
||||
handleMaintainerAccessComment(github, context, maintainerAccess)
|
||||
]);
|
||||
// Handle reviews
|
||||
await handleReviews(github, context, finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, MAX_LABELS, TOO_BIG_THRESHOLD);
|
||||
|
||||
// Apply labels
|
||||
await applyLabels(github, context, finalLabels);
|
||||
|
||||
92
.github/scripts/auto-label-pr/reviews.js
vendored
92
.github/scripts/auto-label-pr/reviews.js
vendored
@@ -2,8 +2,7 @@ const {
|
||||
BOT_COMMENT_MARKER,
|
||||
CODEOWNERS_MARKER,
|
||||
TOO_BIG_MARKER,
|
||||
DEPRECATED_COMPONENT_MARKER,
|
||||
ORG_FORK_MARKER
|
||||
DEPRECATED_COMPONENT_MARKER
|
||||
} = require('./constants');
|
||||
|
||||
// Generate review messages
|
||||
@@ -41,36 +40,16 @@ function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo,
|
||||
|
||||
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
||||
|
||||
message +=
|
||||
`Hey @${prAuthor}, thanks for the contribution! Just a heads up, ` +
|
||||
`this PR is on the large side `;
|
||||
|
||||
if (tooManyLabels && tooManyChanges) {
|
||||
message +=
|
||||
`(${nonTestChanges} line changes excluding tests, across ` +
|
||||
`${originalLabelCount} different components/areas)`;
|
||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
|
||||
} else if (tooManyLabels) {
|
||||
message +=
|
||||
`(it touches ${originalLabelCount} different components/areas)`;
|
||||
message += `This PR affects ${originalLabelCount} different components/areas.`;
|
||||
} else {
|
||||
message += `(${nonTestChanges} line changes excluding tests)`;
|
||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
|
||||
}
|
||||
|
||||
message += `, which makes it harder for maintainers to review.\n\n`;
|
||||
message +=
|
||||
`Smaller, focused PRs tend to be reviewed much faster since they ` +
|
||||
`fit into the short gaps between other maintainer work; large ones ` +
|
||||
`often have to wait for a rare long uninterrupted block of time. ` +
|
||||
`If you can break this up into smaller pieces that can be reviewed ` +
|
||||
`independently, it will almost certainly land faster overall.\n\n`;
|
||||
message +=
|
||||
`Before putting more time in, it's also worth popping into ` +
|
||||
`\`#devs\` on [Discord](https://esphome.io/chat) so we can help ` +
|
||||
`you scope things and flag anything already in flight.\n\n`;
|
||||
message +=
|
||||
`For more details (including how to split the work up), see: ` +
|
||||
`https://developers.esphome.io/contributing/submitting-your-work/` +
|
||||
`#how-to-approach-large-submissions`;
|
||||
message += ` Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.\n\n`;
|
||||
message += `For guidance on breaking down large PRs, see: https://developers.esphome.io/contributing/submitting-your-work/#how-to-approach-large-submissions`;
|
||||
|
||||
messages.push(message);
|
||||
}
|
||||
@@ -157,63 +136,6 @@ async function handleReviews(github, context, finalLabels, originalLabelCount, d
|
||||
}
|
||||
}
|
||||
|
||||
// Handle maintainer access warning comment
|
||||
async function handleMaintainerAccessComment(github, context, maintainerAccess) {
|
||||
if (!maintainerAccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { owner, repo } = context.repo;
|
||||
const pr_number = context.issue.number;
|
||||
const prAuthor = context.payload.pull_request.user.login;
|
||||
|
||||
// Check if we already posted the warning (iterate pages to exit early)
|
||||
let existingComment;
|
||||
for await (const { data: comments } of github.paginate.iterator(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number: pr_number }
|
||||
)) {
|
||||
existingComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body && comment.body.includes(ORG_FORK_MARKER)
|
||||
);
|
||||
if (existingComment) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingComment) {
|
||||
console.log('Maintainer access warning comment already exists, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
let body;
|
||||
if (maintainerAccess.isOrgFork) {
|
||||
body = `${ORG_FORK_MARKER}\n### ⚠️ Organization Fork Detected\n\n` +
|
||||
`Hey there @${prAuthor},\n` +
|
||||
`It looks like this PR was submitted from a fork owned by the **${maintainerAccess.orgName}** organization. ` +
|
||||
`GitHub does not allow maintainers to push changes to pull request branches when the fork is owned by an organization. ` +
|
||||
`This means we won't be able to make small adjustments or fixups to your PR directly.\n\n` +
|
||||
`To allow maintainer collaboration, please re-submit this PR from a personal fork instead.\n\n` +
|
||||
`See: [Setting up the local repository](https://developers.esphome.io/contributing/development-environment/?h=org#set-up-the-local-repository) for more details.`;
|
||||
} else {
|
||||
body = `${ORG_FORK_MARKER}\n### ⚠️ Maintainer Access Disabled\n\n` +
|
||||
`Hey there @${prAuthor},\n` +
|
||||
`It looks like this PR does not have the "Allow edits from maintainers" option enabled. ` +
|
||||
`This means we won't be able to make small adjustments or fixups to your PR directly.\n\n` +
|
||||
`Please enable this option in the PR sidebar to allow maintainer collaboration.`;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr_number,
|
||||
body
|
||||
});
|
||||
console.log('Created maintainer access warning comment');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
handleReviews,
|
||||
handleMaintainerAccessComment
|
||||
handleReviews
|
||||
};
|
||||
|
||||
6
.github/workflows/auto-label-pr.yml
vendored
6
.github/workflows/auto-label-pr.yml
vendored
@@ -20,20 +20,20 @@ env:
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.state == 'open' && (github.event.action != 'labeled' || github.event.sender.type != 'Bot')
|
||||
if: github.event.action != 'labeled' || github.event.sender.type != 'Bot'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v2
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v2
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Auto Label PR
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
6
.github/workflows/ci-api-proto.yml
vendored
6
.github/workflows/ci-api-proto.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
fi
|
||||
- if: failure()
|
||||
name: Review PR
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
run: git diff
|
||||
- if: failure()
|
||||
name: Archive artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: generated-proto-files
|
||||
path: |
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
esphome/components/api/api_pb2_service.*
|
||||
- if: success()
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
|
||||
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
4
.github/workflows/ci-clang-tidy-hash.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- if: failure() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Request changes
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
await github.rest.pulls.createReview({
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
- if: success() && github.event.pull_request.head.repo.full_name == github.repository
|
||||
name: Dismiss review
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
let reviews = await github.rest.pulls.listReviews({
|
||||
|
||||
76
.github/workflows/ci.yml
vendored
76
.github/workflows/ci.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Generate cache-key
|
||||
id: cache-key
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_dev.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
# yamllint disable-line rule:line-length
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_dev.txt -r requirements_test.txt pre-commit
|
||||
pip install -r requirements.txt -r requirements_test.txt pre-commit
|
||||
pip install -e .
|
||||
|
||||
pylint:
|
||||
@@ -108,34 +108,6 @@ jobs:
|
||||
script/generate-esp32-boards.py --check
|
||||
script/generate-rp2040-boards.py --check
|
||||
|
||||
import-time:
|
||||
name: Check import esphome.__main__ time
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- common
|
||||
- determine-jobs
|
||||
if: needs.determine-jobs.outputs.import-time == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Restore Python
|
||||
uses: ./.github/actions/restore-python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Check import time against budget and write waterfall HAR
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
script/check_import_time.py --check --har importtime.har
|
||||
- name: Upload waterfall HAR
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: import-time-waterfall
|
||||
path: importtime.har
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
|
||||
pytest:
|
||||
name: Run pytest
|
||||
strategy:
|
||||
@@ -187,7 +159,7 @@ jobs:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Save Python virtual environment cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -204,7 +176,6 @@ jobs:
|
||||
clang-tidy: ${{ steps.determine.outputs.clang-tidy }}
|
||||
clang-tidy-mode: ${{ steps.determine.outputs.clang-tidy-mode }}
|
||||
python-linters: ${{ steps.determine.outputs.python-linters }}
|
||||
import-time: ${{ steps.determine.outputs.import-time }}
|
||||
changed-components: ${{ steps.determine.outputs.changed-components }}
|
||||
changed-components-with-tests: ${{ steps.determine.outputs.changed-components-with-tests }}
|
||||
directly-changed-components-with-tests: ${{ steps.determine.outputs.directly-changed-components-with-tests }}
|
||||
@@ -227,7 +198,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Restore components graph cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -248,7 +219,6 @@ jobs:
|
||||
echo "clang-tidy=$(echo "$output" | jq -r '.clang_tidy')" >> $GITHUB_OUTPUT
|
||||
echo "clang-tidy-mode=$(echo "$output" | jq -r '.clang_tidy_mode')" >> $GITHUB_OUTPUT
|
||||
echo "python-linters=$(echo "$output" | jq -r '.python_linters')" >> $GITHUB_OUTPUT
|
||||
echo "import-time=$(echo "$output" | jq -r '.import_time')" >> $GITHUB_OUTPUT
|
||||
echo "changed-components=$(echo "$output" | jq -c '.changed_components')" >> $GITHUB_OUTPUT
|
||||
echo "changed-components-with-tests=$(echo "$output" | jq -c '.changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||
echo "directly-changed-components-with-tests=$(echo "$output" | jq -c '.directly_changed_components_with_tests')" >> $GITHUB_OUTPUT
|
||||
@@ -261,7 +231,7 @@ jobs:
|
||||
echo "benchmarks=$(echo "$output" | jq -r '.benchmarks')" >> $GITHUB_OUTPUT
|
||||
- name: Save components graph cache
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: .temp/components_graph.json
|
||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||
@@ -283,7 +253,7 @@ jobs:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
@@ -369,7 +339,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@c381be0bfd20e844fb45594f6aa182ffcd94545c # v4.15.0
|
||||
uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4
|
||||
with:
|
||||
run: ${{ steps.build.outputs.binary }}
|
||||
mode: simulation
|
||||
@@ -417,14 +387,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -496,14 +466,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -585,14 +555,14 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache platformio
|
||||
if: github.ref != 'refs/heads/dev'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||
@@ -847,7 +817,7 @@ jobs:
|
||||
- name: Restore cached memory analysis
|
||||
id: cache-memory-analysis
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -871,7 +841,7 @@ jobs:
|
||||
|
||||
- name: Cache platformio
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -898,8 +868,7 @@ jobs:
|
||||
python script/test_build_components.py \
|
||||
-e compile \
|
||||
-c "$component_list" \
|
||||
-t "$platform" \
|
||||
--base-only 2>&1 | \
|
||||
-t "$platform" 2>&1 | \
|
||||
tee /dev/stderr | \
|
||||
python script/ci_memory_impact_extract.py \
|
||||
--output-env \
|
||||
@@ -913,7 +882,7 @@ jobs:
|
||||
|
||||
- name: Save memory analysis to cache
|
||||
if: steps.check-script.outputs.skip != 'true' && steps.check-tests.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: memory-analysis-target.json
|
||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||
@@ -934,7 +903,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: memory-analysis-target
|
||||
path: memory-analysis-target.json
|
||||
@@ -960,7 +929,7 @@ jobs:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||
- name: Cache platformio
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||
@@ -985,8 +954,7 @@ jobs:
|
||||
python script/test_build_components.py \
|
||||
-e compile \
|
||||
-c "$component_list" \
|
||||
-t "$platform" \
|
||||
--base-only 2>&1 | \
|
||||
-t "$platform" 2>&1 | \
|
||||
tee /dev/stderr | \
|
||||
python script/ci_memory_impact_extract.py \
|
||||
--output-env \
|
||||
@@ -999,7 +967,7 @@ jobs:
|
||||
--platform "$platform"
|
||||
|
||||
- name: Upload memory analysis JSON
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: memory-analysis-pr
|
||||
path: memory-analysis-pr.json
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Close PR From Fork Default Branch
|
||||
|
||||
on:
|
||||
# pull_request_target is required so we have permission to comment and close PRs from forks.
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
close:
|
||||
name: Close PR opened from fork's default branch
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
&& github.event.pull_request.head.ref == github.event.repository.default_branch
|
||||
steps:
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const author = context.payload.pull_request.user.login;
|
||||
const defaultBranch = context.payload.repository.default_branch;
|
||||
const headRepo = context.payload.pull_request.head.repo.full_name;
|
||||
|
||||
const body = [
|
||||
`Hi @${author}, thanks for opening a pull request! :tada:`,
|
||||
``,
|
||||
`It looks like this PR was opened from the \`${defaultBranch}\` branch of your fork (\`${headRepo}\`), which is the same name as this repository's default branch. Working directly on \`${defaultBranch}\` in your fork causes a few problems:`,
|
||||
``,
|
||||
`- Your fork's \`${defaultBranch}\` branch will permanently diverge from \`esphome/esphome:${defaultBranch}\`, making it hard to keep your fork up to date.`,
|
||||
`- Any additional commits you push to \`${defaultBranch}\` will be added to this PR, so you can't easily work on multiple changes at once.`,
|
||||
`- Pushing maintainer fixes to your branch is awkward, since it means committing directly to your fork's default branch.`,
|
||||
`- It makes local collaboration painful — \`${defaultBranch}\` in a checkout becomes ambiguous between upstream and your fork, and maintainers end up with naming collisions when fetching your branch.`,
|
||||
``,
|
||||
`Please re-open this as a new PR from a dedicated feature branch. The usual flow looks like:`,
|
||||
``,
|
||||
`\`\`\`bash`,
|
||||
`# Make sure your fork's ${defaultBranch} is up to date with upstream`,
|
||||
`git remote add upstream https://github.com/${owner}/${repo}.git # if you haven't already`,
|
||||
`git fetch upstream`,
|
||||
`git checkout ${defaultBranch}`,
|
||||
`git reset --hard upstream/${defaultBranch}`,
|
||||
`git push --force-with-lease origin ${defaultBranch}`,
|
||||
``,
|
||||
`# Create a new branch for your change and cherry-pick / re-apply your commits there`,
|
||||
`git checkout -b my-feature-branch upstream/${defaultBranch}`,
|
||||
`# ...re-apply your changes, then:`,
|
||||
`git push origin my-feature-branch`,
|
||||
`\`\`\``,
|
||||
``,
|
||||
`Then open a new pull request from \`my-feature-branch\` into \`${owner}/${repo}:${defaultBranch}\`.`,
|
||||
``,
|
||||
`Closing this PR for now — sorry for the friction, and thanks again for contributing! :heart:`,
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
state: 'closed',
|
||||
});
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
CODEOWNERS
|
||||
|
||||
- name: Check codeowner approval and update label
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
with:
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
- name: Request reviews from component codeowners
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const { loadCodeowners, getEffectiveOwners } = require('./.github/scripts/codeowners.js');
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
2
.github/workflows/external-component-bot.yml
vendored
2
.github/workflows/external-component-bot.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add external component comment
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
||||
2
.github/workflows/issue-codeowner-notify.yml
vendored
2
.github/workflows/issue-codeowner-notify.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify codeowners for component issues
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
|
||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -8,4 +8,4 @@ on:
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@3c4e8446aa1029f1c346a482034b3ee1489077ca # 2026.4.0
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@main
|
||||
|
||||
2
.github/workflows/pr-title-check.yml
vendored
2
.github/workflows/pr-title-check.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const {
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -138,7 +138,7 @@ jobs:
|
||||
# version: ${{ needs.init.outputs.tag }}
|
||||
|
||||
- name: Upload digests
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: digests-${{ matrix.platform.arch }}
|
||||
path: /tmp/digests
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -229,7 +229,7 @@ jobs:
|
||||
repositories: home-assistant-addon
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -264,7 +264,7 @@ jobs:
|
||||
repositories: esphome-schema
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
@@ -287,7 +287,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
|
||||
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
||||
with:
|
||||
app-id: ${{ secrets.ESPHOME_GITHUB_APP_ID }}
|
||||
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
repositories: version-notifier
|
||||
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
|
||||
27
.github/workflows/status-check-labels.yml
vendored
27
.github/workflows/status-check-labels.yml
vendored
@@ -2,29 +2,30 @@ name: Status check labels
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, labeled, unlabeled, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check blocking labels
|
||||
name: Check ${{ matrix.label }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
label:
|
||||
- needs-docs
|
||||
- merge-after-release
|
||||
- chained-pr
|
||||
steps:
|
||||
- name: Check for blocking labels
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
- name: Check for ${{ matrix.label }} label
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const blockingLabels = ['needs-docs', 'merge-after-release', 'chained-pr'];
|
||||
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number
|
||||
});
|
||||
const labelNames = labels.map(l => l.name);
|
||||
const found = blockingLabels.filter(bl => labelNames.includes(bl));
|
||||
if (found.length > 0) {
|
||||
core.setFailed(`Pull request cannot be merged, it has blocking label(s): ${found.join(', ')}`);
|
||||
const hasLabel = labels.find(label => label.name === '${{ matrix.label }}');
|
||||
if (hasLabel) {
|
||||
core.setFailed('Pull request cannot be merged, it is labeled as ${{ matrix.label }}');
|
||||
}
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
python script/run-in-env.py pre-commit run --all-files
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@openhomefoundation.org>
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -146,6 +146,5 @@ sdkconfig.*
|
||||
|
||||
/components
|
||||
/managed_components
|
||||
/dependencies.lock
|
||||
|
||||
api-docs/
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.12
|
||||
rev: v0.15.9
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -58,7 +58,6 @@ repos:
|
||||
entry: python3 script/run-in-env.py pylint
|
||||
language: system
|
||||
types: [python]
|
||||
files: ^esphome/.+\.py$
|
||||
- id: clang-tidy-hash
|
||||
name: Update clang-tidy hash
|
||||
entry: python script/clang_tidy_hash.py --update-if-changed
|
||||
|
||||
10
CODEOWNERS
10
CODEOWNERS
@@ -56,7 +56,6 @@ esphome/components/audio_adc/* @kbx81
|
||||
esphome/components/audio_dac/* @kbx81
|
||||
esphome/components/audio_file/* @kahrendt
|
||||
esphome/components/audio_file/media_source/* @kahrendt
|
||||
esphome/components/audio_http/* @kahrendt
|
||||
esphome/components/axs15231/* @clydebarrow
|
||||
esphome/components/b_parasite/* @rbaron
|
||||
esphome/components/ballu/* @bazuchan
|
||||
@@ -347,7 +346,6 @@ esphome/components/modbus_controller/select/* @martgras @stegm
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
esphome/components/modbus_server/* @exciton
|
||||
esphome/components/mopeka_ble/* @Fabian-Schmidt @spbrogan
|
||||
esphome/components/mopeka_pro_check/* @spbrogan
|
||||
esphome/components/mopeka_std_check/* @Fabian-Schmidt
|
||||
@@ -405,7 +403,6 @@ esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/qspi_dbi/* @clydebarrow
|
||||
esphome/components/qwiic_pir/* @kahrendt
|
||||
esphome/components/radio_frequency/* @kbx81
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
@@ -441,11 +438,6 @@ esphome/components/sen0321/* @notjj
|
||||
esphome/components/sen21231/* @shreyaskarnik
|
||||
esphome/components/sen5x/* @martgras
|
||||
esphome/components/sen6x/* @martgras @mebner86 @mikelawrence @tuct
|
||||
esphome/components/sendspin/* @kahrendt
|
||||
esphome/components/sendspin/media_player/* @kahrendt
|
||||
esphome/components/sendspin/media_source/* @kahrendt
|
||||
esphome/components/sendspin/sensor/* @kahrendt
|
||||
esphome/components/sendspin/text_sensor/* @kahrendt
|
||||
esphome/components/sensirion_common/* @martgras
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/serial_proxy/* @kbx81
|
||||
@@ -607,6 +599,6 @@ esphome/components/xxtea/* @clydebarrow
|
||||
esphome/components/zephyr/* @tomaszduda23
|
||||
esphome/components/zephyr_mcumgr/ota/* @tomaszduda23
|
||||
esphome/components/zhlt01/* @cfeenstra1024
|
||||
esphome/components/zigbee/* @luar123 @tomaszduda23
|
||||
esphome/components/zigbee/* @tomaszduda23
|
||||
esphome/components/zio_ultrasonic/* @kahrendt
|
||||
esphome/components/zwave_proxy/* @kbx81
|
||||
|
||||
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2026.5.0-dev
|
||||
PROJECT_NUMBER = 2026.4.4
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
@@ -4,5 +4,4 @@ include requirements.txt
|
||||
recursive-include esphome *.yaml
|
||||
recursive-include esphome *.cpp *.h *.tcc *.c
|
||||
recursive-include esphome *.py.script
|
||||
recursive-include esphome *.jinja
|
||||
recursive-include esphome LICENSE.txt
|
||||
|
||||
@@ -39,7 +39,6 @@ from esphome.const import (
|
||||
CONF_MDNS,
|
||||
CONF_MQTT,
|
||||
CONF_NAME,
|
||||
CONF_NAME_ADD_MAC_SUFFIX,
|
||||
CONF_OTA,
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
@@ -72,7 +71,6 @@ from esphome.util import (
|
||||
run_external_process,
|
||||
safe_print,
|
||||
)
|
||||
from esphome.zeroconf import discover_mdns_devices
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -206,64 +204,6 @@ def _resolve_with_cache(address: str, purpose: Purpose) -> list[str]:
|
||||
return [address]
|
||||
|
||||
|
||||
def _populate_mdns_cache(hosts_to_addresses: dict[str, list[str]]) -> None:
|
||||
"""Store discovered ``host -> [ips]`` entries in ``CORE.address_cache``.
|
||||
|
||||
Ensures ``CORE.address_cache`` exists, then records each mDNS hostname so
|
||||
the downstream resolution path (``resolve_ip_address``) can skip opening a
|
||||
second Zeroconf client.
|
||||
"""
|
||||
from esphome.address_cache import AddressCache
|
||||
|
||||
if CORE.address_cache is None:
|
||||
CORE.address_cache = AddressCache()
|
||||
for host, addresses in hosts_to_addresses.items():
|
||||
if addresses:
|
||||
_LOGGER.debug("Caching mDNS result %s -> %s", host, addresses)
|
||||
CORE.address_cache.add_mdns_addresses(host, addresses)
|
||||
|
||||
|
||||
def _discover_mac_suffix_devices() -> list[str] | None:
|
||||
"""Discover ``<name>-<mac>.local`` devices and cache their IPs.
|
||||
|
||||
Returns:
|
||||
- ``None`` when discovery isn't applicable (``name_add_mac_suffix`` off,
|
||||
mDNS disabled, or ``CORE.address`` is already an IP). Callers should
|
||||
then fall back to whatever default OTA address they normally use.
|
||||
- ``[]`` when discovery ran but found nothing. Callers should NOT fall
|
||||
back to the base name: with ``name_add_mac_suffix`` enabled, the base
|
||||
name by definition doesn't exist on the network.
|
||||
- A non-empty sorted list of ``.local`` hostnames on success.
|
||||
|
||||
Populates ``CORE.address_cache`` so downstream resolution (``espota2`` or
|
||||
``aioesphomeapi`` via :func:`_resolve_network_devices`) reuses the IPs we
|
||||
already have without opening a second Zeroconf client.
|
||||
"""
|
||||
if not (has_name_add_mac_suffix() and has_mdns() and has_non_ip_address()):
|
||||
return None
|
||||
_LOGGER.info("Discovering devices...")
|
||||
if not (discovered := discover_mdns_devices(CORE.name)):
|
||||
_LOGGER.warning(
|
||||
"No devices matching '%s-<mac>.local' were discovered.", CORE.name
|
||||
)
|
||||
return []
|
||||
_populate_mdns_cache(discovered)
|
||||
return list(discovered)
|
||||
|
||||
|
||||
def _ota_hostnames_for_default(purpose: Purpose) -> list[str]:
|
||||
"""Return OTA hostname(s) for the ``--device OTA`` / default-resolve path.
|
||||
|
||||
When ``name_add_mac_suffix`` is enabled, returns discovered
|
||||
``<name>-<mac>.local`` hostnames (possibly empty — in which case the
|
||||
caller should not fall back to the base name). Otherwise falls back to
|
||||
the cache-resolved ``CORE.address``.
|
||||
"""
|
||||
if (discovered := _discover_mac_suffix_devices()) is not None:
|
||||
return discovered
|
||||
return _resolve_with_cache(CORE.address, purpose)
|
||||
|
||||
|
||||
def choose_upload_log_host(
|
||||
default: list[str] | str | None,
|
||||
check_default: str | None,
|
||||
@@ -302,14 +242,14 @@ def choose_upload_log_host(
|
||||
resolved.append("MQTT")
|
||||
|
||||
if has_api() and has_non_ip_address() and has_resolvable_address():
|
||||
resolved.extend(_ota_hostnames_for_default(purpose))
|
||||
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||
|
||||
elif purpose == Purpose.UPLOADING:
|
||||
if has_ota() and has_mqtt_ip_lookup():
|
||||
resolved.append("MQTTIP")
|
||||
|
||||
if has_ota() and has_non_ip_address() and has_resolvable_address():
|
||||
resolved.extend(_ota_hostnames_for_default(purpose))
|
||||
resolved.extend(_resolve_with_cache(CORE.address, purpose))
|
||||
else:
|
||||
resolved.append(device)
|
||||
if not resolved:
|
||||
@@ -341,29 +281,22 @@ def choose_upload_log_host(
|
||||
elif bootsel.permission_error:
|
||||
bootsel_permission_error = True
|
||||
|
||||
def add_ota_options() -> None:
|
||||
"""Add OTA options, using mDNS discovery if name_add_mac_suffix is enabled."""
|
||||
if (discovered := _discover_mac_suffix_devices()) is not None:
|
||||
# Discovery was applicable. Use whatever we found — on empty,
|
||||
# intentionally skip the base-name fallback since with
|
||||
# name_add_mac_suffix on, the base name doesn't exist on the net.
|
||||
for host in discovered:
|
||||
options.append((f"Over The Air ({host})", host))
|
||||
elif has_resolvable_address():
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if has_mqtt_ip_lookup():
|
||||
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
|
||||
|
||||
if purpose == Purpose.LOGGING:
|
||||
if has_mqtt_logging():
|
||||
mqtt_config = CORE.config[CONF_MQTT]
|
||||
options.append((f"MQTT ({mqtt_config[CONF_BROKER]})", "MQTT"))
|
||||
|
||||
if has_api():
|
||||
add_ota_options()
|
||||
if has_resolvable_address():
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if has_mqtt_ip_lookup():
|
||||
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
|
||||
|
||||
elif purpose == Purpose.UPLOADING and has_ota():
|
||||
add_ota_options()
|
||||
if has_resolvable_address():
|
||||
options.append((f"Over The Air ({CORE.address})", CORE.address))
|
||||
if has_mqtt_ip_lookup():
|
||||
options.append(("Over The Air (MQTT IP lookup)", "MQTTIP"))
|
||||
|
||||
# Show helpful BOOTSEL instructions for RP2040 when no BOOTSEL device is found
|
||||
if (
|
||||
@@ -474,17 +407,7 @@ def has_resolvable_address() -> bool:
|
||||
return not CORE.address.endswith(".local")
|
||||
|
||||
|
||||
def has_name_add_mac_suffix() -> bool:
|
||||
"""Check if name_add_mac_suffix is enabled in the config."""
|
||||
if CORE.config is None:
|
||||
return False
|
||||
esphome_config = CORE.config.get(CONF_ESPHOME, {})
|
||||
return esphome_config.get(CONF_NAME_ADD_MAC_SUFFIX, False)
|
||||
|
||||
|
||||
def mqtt_get_ip(
|
||||
config: ConfigType, username: str, password: str, client_id: str
|
||||
) -> list[str]:
|
||||
def mqtt_get_ip(config: ConfigType, username: str, password: str, client_id: str):
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.get_esphome_device_ip(config, username, password, client_id)
|
||||
@@ -497,9 +420,6 @@ def _resolve_network_devices(
|
||||
|
||||
This function filters the devices list to:
|
||||
- Replace MQTT/MQTTIP magic strings with actual IP addresses via MQTT lookup
|
||||
- Expand hostnames that are already in ``CORE.address_cache`` to their
|
||||
cached IPs so downstream code (e.g. aioesphomeapi) doesn't open a second
|
||||
Zeroconf client to resolve them
|
||||
- Deduplicate addresses while preserving order
|
||||
- Only resolve MQTT once even if multiple MQTT strings are present
|
||||
- If MQTT resolution fails, log a warning and continue with other devices
|
||||
@@ -524,29 +444,13 @@ def _resolve_network_devices(
|
||||
mqtt_ips = mqtt_get_ip(
|
||||
config, args.username, args.password, args.client_id
|
||||
)
|
||||
# pylint can't infer mqtt_get_ip's return through its
|
||||
# lazy ``from esphome import mqtt`` import, so it flags
|
||||
# the genexpr below.
|
||||
network_devices.extend(
|
||||
addr
|
||||
for addr in mqtt_ips # pylint: disable=not-an-iterable
|
||||
if addr not in network_devices
|
||||
)
|
||||
network_devices.extend(mqtt_ips)
|
||||
except EsphomeError as err:
|
||||
_LOGGER.warning(
|
||||
"MQTT IP discovery failed (%s), will try other devices if available",
|
||||
err,
|
||||
)
|
||||
mqtt_resolved = True
|
||||
continue
|
||||
|
||||
# If the hostname is already in the address cache (e.g. populated by
|
||||
# mDNS discovery), substitute the cached IPs so aioesphomeapi doesn't
|
||||
# open its own Zeroconf to re-resolve it.
|
||||
if CORE.address_cache and (cached := CORE.address_cache.get_addresses(device)):
|
||||
network_devices.extend(
|
||||
addr for addr in cached if addr not in network_devices
|
||||
)
|
||||
elif device not in network_devices:
|
||||
# Regular network address or IP - add if not already present
|
||||
network_devices.append(device)
|
||||
|
||||
@@ -101,17 +101,6 @@ class AddressCache:
|
||||
"""Check if any cache entries exist."""
|
||||
return bool(self.mdns_cache or self.dns_cache)
|
||||
|
||||
def add_mdns_addresses(self, hostname: str, addresses: list[str]) -> None:
|
||||
"""Store resolved mDNS addresses for ``hostname`` in the cache.
|
||||
|
||||
Callers that discover ``.local`` hosts (e.g. via mDNS browse) can use
|
||||
this to avoid a second resolution round-trip during the upload path.
|
||||
No-op when ``addresses`` is empty.
|
||||
"""
|
||||
if not addresses:
|
||||
return
|
||||
self.mdns_cache[normalize_hostname(hostname)] = addresses
|
||||
|
||||
@classmethod
|
||||
def from_cli_args(
|
||||
cls, mdns_args: Iterable[str], dns_args: Iterable[str]
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"""Helpers for running an async coroutine from sync code via a daemon thread.
|
||||
|
||||
``asyncio.run(coro())`` in the main thread blocks until the loop's cleanup
|
||||
cycle finishes, which can add hundreds of milliseconds before the caller
|
||||
receives the result. Running the loop in a daemon thread lets the caller
|
||||
observe the result as soon as the coroutine completes while cleanup finishes
|
||||
in the background.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
import threading
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class AsyncThreadRunner(threading.Thread, Generic[_T]):
|
||||
"""Run an async coroutine in a daemon thread and expose its result.
|
||||
|
||||
The runner catches all exceptions from the coroutine and stores them in
|
||||
``exception`` so ``event`` is always set — this prevents callers waiting
|
||||
on ``event`` from hanging forever when the coroutine crashes.
|
||||
|
||||
Typical usage::
|
||||
|
||||
runner = AsyncThreadRunner(lambda: my_coro(arg))
|
||||
runner.start()
|
||||
if not runner.event.wait(timeout=5.0):
|
||||
... # timed out
|
||||
if runner.exception is not None:
|
||||
raise runner.exception
|
||||
result = runner.result
|
||||
"""
|
||||
|
||||
def __init__(self, coro_factory: Callable[[], Awaitable[_T]]) -> None:
|
||||
super().__init__(daemon=True)
|
||||
self._coro_factory = coro_factory
|
||||
self.result: _T | None = None
|
||||
self.exception: BaseException | None = None
|
||||
self.event = threading.Event()
|
||||
|
||||
async def _runner(self) -> None:
|
||||
try:
|
||||
self.result = await self._coro_factory()
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# Capture all exceptions so ``event`` is always set — otherwise a
|
||||
# crash would hang the waiter forever.
|
||||
self.exception = exc
|
||||
finally:
|
||||
self.event.set()
|
||||
|
||||
def run(self) -> None:
|
||||
asyncio.run(self._runner())
|
||||
@@ -199,10 +199,11 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
|
||||
return cv.Schema([schema])(value)
|
||||
except cv.Invalid as err2:
|
||||
if "extra keys not allowed" in str(err2) and len(err2.path) == 2:
|
||||
raise err from None
|
||||
# pylint: disable=raise-missing-from
|
||||
raise err
|
||||
if "Unable to find action" in str(err):
|
||||
raise err2 from None
|
||||
raise cv.MultipleInvalid([err, err2]) from None
|
||||
raise err2
|
||||
raise cv.MultipleInvalid([err, err2])
|
||||
elif isinstance(value, dict):
|
||||
if CONF_THEN in value:
|
||||
return [schema(value)]
|
||||
|
||||
@@ -190,7 +190,7 @@ void AcDimmer::setup() {
|
||||
this->zero_cross_pin_->setup();
|
||||
this->store_.zero_cross_pin = this->zero_cross_pin_->to_isr();
|
||||
this->zero_cross_pin_->attach_interrupt(&AcDimmerDataStore::s_gpio_intr, &this->store_,
|
||||
this->zero_cross_interrupt_type_);
|
||||
gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
@@ -226,25 +226,19 @@ void AcDimmer::write_state(float state) {
|
||||
void AcDimmer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"AcDimmer:\n"
|
||||
" Min Power: %.1f%%\n"
|
||||
" Init with half cycle: %s",
|
||||
" Min Power: %.1f%%\n"
|
||||
" Init with half cycle: %s",
|
||||
this->store_.min_power / 10.0f, YESNO(this->init_with_half_cycle_));
|
||||
LOG_PIN(" Output Pin: ", this->gate_pin_);
|
||||
LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_);
|
||||
if (this->zero_cross_interrupt_type_ == gpio::INTERRUPT_RISING_EDGE) {
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: rising");
|
||||
} else if (this->zero_cross_interrupt_type_ == gpio::INTERRUPT_FALLING_EDGE) {
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: falling");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: any");
|
||||
}
|
||||
if (method_ == DIM_METHOD_LEADING_PULSE) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
ESP_LOGCONFIG(TAG, " Method: leading pulse");
|
||||
} else if (method_ == DIM_METHOD_LEADING) {
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
ESP_LOGCONFIG(TAG, " Method: leading");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
ESP_LOGCONFIG(TAG, " Method: trailing");
|
||||
}
|
||||
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
void dump_config() override;
|
||||
void set_gate_pin(InternalGPIOPin *gate_pin) { gate_pin_ = gate_pin; }
|
||||
void set_zero_cross_pin(InternalGPIOPin *zero_cross_pin) { zero_cross_pin_ = zero_cross_pin; }
|
||||
void set_zero_cross_interrupt_type(gpio::InterruptType type) { zero_cross_interrupt_type_ = type; }
|
||||
void set_init_with_half_cycle(bool init_with_half_cycle) { init_with_half_cycle_ = init_with_half_cycle; }
|
||||
void set_method(DimMethod method) { method_ = method; }
|
||||
|
||||
@@ -57,7 +56,6 @@ class AcDimmer : public output::FloatOutput, public Component {
|
||||
|
||||
InternalGPIOPin *gate_pin_;
|
||||
InternalGPIOPin *zero_cross_pin_;
|
||||
gpio::InterruptType zero_cross_interrupt_type_;
|
||||
AcDimmerDataStore store_;
|
||||
bool init_with_half_cycle_;
|
||||
DimMethod method_;
|
||||
|
||||
@@ -7,8 +7,6 @@ from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
|
||||
gpio_ns = cg.esphome_ns.namespace("gpio")
|
||||
|
||||
ac_dimmer_ns = cg.esphome_ns.namespace("ac_dimmer")
|
||||
AcDimmer = ac_dimmer_ns.class_("AcDimmer", output.FloatOutput, cg.Component)
|
||||
|
||||
@@ -19,26 +17,15 @@ DIM_METHODS = {
|
||||
"TRAILING": DimMethod.DIM_METHOD_TRAILING,
|
||||
}
|
||||
|
||||
ZC_INTERRUPT_TYPES = {
|
||||
"RISING": gpio_ns.INTERRUPT_RISING_EDGE,
|
||||
"FALLING": gpio_ns.INTERRUPT_FALLING_EDGE,
|
||||
"ANY": gpio_ns.INTERRUPT_ANY_EDGE,
|
||||
}
|
||||
|
||||
CONF_GATE_PIN = "gate_pin"
|
||||
CONF_ZERO_CROSS_PIN = "zero_cross_pin"
|
||||
CONF_INIT_WITH_HALF_CYCLE = "init_with_half_cycle"
|
||||
CONF_ZERO_CROSS_INTERRUPT_TYPE = "zero_cross_interrupt_type"
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(AcDimmer),
|
||||
cv.Required(CONF_GATE_PIN): pins.internal_gpio_output_pin_schema,
|
||||
cv.Required(CONF_ZERO_CROSS_PIN): pins.internal_gpio_input_pin_schema,
|
||||
cv.Optional(CONF_ZERO_CROSS_INTERRUPT_TYPE, default="FALLING"): cv.enum(
|
||||
ZC_INTERRUPT_TYPES, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_INIT_WITH_HALF_CYCLE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_METHOD, default="leading pulse"): cv.enum(
|
||||
DIM_METHODS, upper=True, space="_"
|
||||
@@ -67,6 +54,5 @@ async def to_code(config):
|
||||
cg.add(var.set_gate_pin(pin))
|
||||
pin = await cg.gpio_pin_expression(config[CONF_ZERO_CROSS_PIN])
|
||||
cg.add(var.set_zero_cross_pin(pin))
|
||||
cg.add(var.set_zero_cross_interrupt_type(config[CONF_ZERO_CROSS_INTERRUPT_TYPE]))
|
||||
cg.add(var.set_init_with_half_cycle(config[CONF_INIT_WITH_HALF_CYCLE]))
|
||||
cg.add(var.set_method(config[CONF_METHOD]))
|
||||
|
||||
@@ -8,9 +8,6 @@ namespace ade7953_base {
|
||||
|
||||
static const char *const TAG = "ade7953";
|
||||
|
||||
constexpr uint16_t CONFIG_DEFAULT = 0x8004u;
|
||||
constexpr uint16_t CONFIG_LOCK_BIT = 0x8000u;
|
||||
|
||||
static const float ADE_POWER_FACTOR = 154.0f;
|
||||
static const float ADE_WATTSEC_POWER_FACTOR = ADE_POWER_FACTOR * ADE_POWER_FACTOR / 3600;
|
||||
|
||||
@@ -21,12 +18,7 @@ void ADE7953::setup() {
|
||||
|
||||
// The chip might take up to 100ms to initialise
|
||||
this->set_timeout(100, [this]() {
|
||||
// Lock communication interface (SPI or I2C)
|
||||
uint16_t config_v = CONFIG_DEFAULT;
|
||||
this->ade_read_16(CONFIG_16, &config_v);
|
||||
config_v &= static_cast<uint16_t>(~CONFIG_LOCK_BIT); // Clear the lock bit
|
||||
this->ade_write_16(CONFIG_16, config_v);
|
||||
// Configure optimum settings according to datasheet
|
||||
// this->ade_write_8(0x0010, 0x04);
|
||||
this->ade_write_8(0x00FE, 0xAD);
|
||||
this->ade_write_16(0x0120, 0x0030);
|
||||
// Set gains
|
||||
|
||||
@@ -9,35 +9,31 @@
|
||||
namespace esphome {
|
||||
namespace ade7953_base {
|
||||
|
||||
static constexpr uint8_t PGA_V_8 =
|
||||
static const uint8_t PGA_V_8 =
|
||||
0x007; // PGA_V, (R/W) Default: 0x00, Unsigned, Voltage channel gain configuration (Bits[2:0])
|
||||
static constexpr uint8_t PGA_IA_8 =
|
||||
static const uint8_t PGA_IA_8 =
|
||||
0x008; // PGA_IA, (R/W) Default: 0x00, Unsigned, Current Channel A gain configuration (Bits[2:0])
|
||||
static constexpr uint8_t PGA_IB_8 =
|
||||
static const uint8_t PGA_IB_8 =
|
||||
0x009; // PGA_IB, (R/W) Default: 0x00, Unsigned, Current Channel B gain configuration (Bits[2:0])
|
||||
|
||||
static constexpr uint16_t CONFIG_16 = 0x102; // CONFIG, (R/W) Default: 0x8004, Unsigned, Configuration register
|
||||
|
||||
static constexpr uint16_t AIGAIN_32 =
|
||||
static const uint32_t AIGAIN_32 =
|
||||
0x380; // AIGAIN, (R/W) Default: 0x400000, Unsigned,Current channel gain (Current Channel A)(32 bit)
|
||||
static constexpr uint16_t AVGAIN_32 =
|
||||
0x381; // AVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
|
||||
static constexpr uint16_t AWGAIN_32 =
|
||||
static const uint32_t AVGAIN_32 = 0x381; // AVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
|
||||
static const uint32_t AWGAIN_32 =
|
||||
0x382; // AWGAIN, (R/W) Default: 0x400000, Unsigned,Active power gain (Current Channel A)(32 bit)
|
||||
static constexpr uint16_t AVARGAIN_32 =
|
||||
static const uint32_t AVARGAIN_32 =
|
||||
0x383; // AVARGAIN, (R/W) Default: 0x400000, Unsigned, Reactive power gain (Current Channel A)(32 bit)
|
||||
static constexpr uint16_t AVAGAIN_32 =
|
||||
static const uint32_t AVAGAIN_32 =
|
||||
0x384; // AVAGAIN, (R/W) Default: 0x400000, Unsigned,Apparent power gain (Current Channel A)(32 bit)
|
||||
|
||||
static constexpr uint16_t BIGAIN_32 =
|
||||
static const uint32_t BIGAIN_32 =
|
||||
0x38C; // BIGAIN, (R/W) Default: 0x400000, Unsigned,Current channel gain (Current Channel B)(32 bit)
|
||||
static constexpr uint16_t BVGAIN_32 =
|
||||
0x38D; // BVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
|
||||
static constexpr uint16_t BWGAIN_32 =
|
||||
static const uint32_t BVGAIN_32 = 0x38D; // BVGAIN, (R/W) Default: 0x400000, Unsigned,Voltage channel gain(32 bit)
|
||||
static const uint32_t BWGAIN_32 =
|
||||
0x38E; // BWGAIN, (R/W) Default: 0x400000, Unsigned,Active power gain (Current Channel B)(32 bit)
|
||||
static constexpr uint16_t BVARGAIN_32 =
|
||||
static const uint32_t BVARGAIN_32 =
|
||||
0x38F; // BVARGAIN, (R/W) Default: 0x400000, Unsigned, Reactive power gain (Current Channel B)(32 bit)
|
||||
static constexpr uint16_t BVAGAIN_32 =
|
||||
static const uint32_t BVAGAIN_32 =
|
||||
0x390; // BVAGAIN, (R/W) Default: 0x400000, Unsigned,Apparent power gain (Current Channel B)(32 bit)
|
||||
|
||||
class ADE7953 : public PollingComponent, public sensor::Sensor {
|
||||
|
||||
@@ -7,9 +7,6 @@ namespace ade7953_spi {
|
||||
|
||||
static const char *const TAG = "ade7953";
|
||||
|
||||
// Datasheet requires at least 1.2µs after clearing CONFIG LOCK_BIT before raising CS
|
||||
constexpr uint8_t CONFIG_LOCK_SETTLE_US = 2;
|
||||
|
||||
void AdE7953Spi::setup() {
|
||||
this->spi_setup();
|
||||
ade7953_base::ADE7953::setup();
|
||||
@@ -35,9 +32,6 @@ bool AdE7953Spi::ade_write_16(uint16_t reg, uint16_t value) {
|
||||
this->write_byte16(reg);
|
||||
this->transfer_byte(0);
|
||||
this->write_byte16(value);
|
||||
if (reg == ade7953_base::CONFIG_16) {
|
||||
delayMicroseconds(CONFIG_LOCK_SETTLE_US);
|
||||
}
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace esphome {
|
||||
namespace ade7953_spi {
|
||||
|
||||
class AdE7953Spi : public ade7953_base::ADE7953,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_1MHZ> {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
@@ -13,11 +13,7 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
|
||||
@@ -185,7 +181,7 @@ async def setup_alarm_control_panel_core_(var, config):
|
||||
async def register_alarm_control_panel(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
queue_entity_register("alarm_control_panel", config)
|
||||
cg.add(cg.App.register_alarm_control_panel(var))
|
||||
CORE.register_platform_component("alarm_control_panel", var)
|
||||
await setup_alarm_control_panel_core_(var, config)
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace anova {
|
||||
|
||||
@@ -107,14 +105,14 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
|
||||
}
|
||||
case READ_TARGET_TEMPERATURE:
|
||||
case SET_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); // NOLINT
|
||||
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case READ_CURRENT_TEMPERATURE: {
|
||||
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); // NOLINT
|
||||
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
|
||||
if (this->fahrenheit_)
|
||||
this->current_temp_ = ftoc(this->current_temp_);
|
||||
this->has_current_temp_ = true;
|
||||
|
||||
@@ -72,17 +72,35 @@ APIUnregisterServiceCallAction = api_ns.class_(
|
||||
|
||||
UserServiceTrigger = api_ns.class_("UserServiceTrigger", automation.Trigger)
|
||||
ListEntitiesServicesArgument = api_ns.class_("ListEntitiesServicesArgument")
|
||||
SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = {
|
||||
# Owning element type for each YAML service variable type. Used to derive both
|
||||
# the zero-copy native types and the owning fallback types below.
|
||||
_SERVICE_ARG_SCALAR_TYPES: dict[str, MockObj] = {
|
||||
"bool": cg.bool_,
|
||||
"int": cg.int32,
|
||||
"float": cg.float_,
|
||||
"string": cg.std_string,
|
||||
}
|
||||
SERVICE_ARG_NATIVE_TYPES: dict[str, MockObj] = {
|
||||
# Scalars are passed by value; string uses a non-owning view into rx_buf_.
|
||||
**_SERVICE_ARG_SCALAR_TYPES,
|
||||
"string": cg.StringRef,
|
||||
"bool[]": cg.FixedVector.template(cg.bool_).operator("const").operator("ref"),
|
||||
"int[]": cg.FixedVector.template(cg.int32).operator("const").operator("ref"),
|
||||
"float[]": cg.FixedVector.template(cg.float_).operator("const").operator("ref"),
|
||||
"string[]": cg.FixedVector.template(cg.std_string)
|
||||
.operator("const")
|
||||
.operator("ref"),
|
||||
# Arrays are passed as non-owning const references into rx_buf_.
|
||||
**{
|
||||
f"{name}[]": cg.FixedVector.template(t).operator("const").operator("ref")
|
||||
for name, t in _SERVICE_ARG_SCALAR_TYPES.items()
|
||||
},
|
||||
}
|
||||
# Owning fallback types used when the action chain contains non-synchronous actions
|
||||
# (delay, wait_until, script.wait, etc.). The default non-owning types reference
|
||||
# storage in the receive buffer, which is reused once the synchronous portion of
|
||||
# the chain returns. FixedVector is also non-copyable, so the deferred lambda
|
||||
# capture in DelayAction::play_complex would fail to compile.
|
||||
SERVICE_ARG_FALLBACK_TYPES: dict[str, MockObj] = {
|
||||
"string": cg.std_string,
|
||||
**{
|
||||
f"{name}[]": cg.std_vector.template(t)
|
||||
for name, t in _SERVICE_ARG_SCALAR_TYPES.items()
|
||||
},
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_BATCH_DELAY = "batch_delay"
|
||||
@@ -291,12 +309,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.SplitDefault(
|
||||
CONF_MAX_CONNECTIONS,
|
||||
esp8266=4, # ~40KB free RAM, each connection uses ~500-1000 bytes
|
||||
esp32=5, # 520KB RAM available
|
||||
esp32=8, # 520KB RAM available
|
||||
rp2040=4, # 264KB RAM but LWIP constraints
|
||||
bk72xx=5, # Moderate RAM
|
||||
rtl87xx=5, # Moderate RAM
|
||||
bk72xx=8, # Moderate RAM
|
||||
rtl87xx=8, # Moderate RAM
|
||||
host=8, # Abundant resources
|
||||
ln882x=5, # Moderate RAM
|
||||
ln882x=8, # Moderate RAM
|
||||
): cv.int_range(min=1, max=20),
|
||||
# Maximum queued send buffers per connection before dropping connection
|
||||
# Each buffer uses ~8-12 bytes overhead plus actual message size
|
||||
@@ -336,7 +354,8 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||
if CONF_LISTEN_BACKLOG in config:
|
||||
cg.add(var.set_listen_backlog(config[CONF_LISTEN_BACKLOG]))
|
||||
cg.add_define("MAX_API_CONNECTIONS", config[CONF_MAX_CONNECTIONS])
|
||||
if CONF_MAX_CONNECTIONS in config:
|
||||
cg.add(var.set_max_connections(config[CONF_MAX_CONNECTIONS]))
|
||||
cg.add_define("API_MAX_SEND_QUEUE", config[CONF_MAX_SEND_QUEUE])
|
||||
|
||||
# Set USE_API_USER_DEFINED_ACTIONS if any services are enabled
|
||||
@@ -381,17 +400,20 @@ async def to_code(config: ConfigType) -> None:
|
||||
func_args.append((cg.bool_, "return_response"))
|
||||
|
||||
# Check if action chain has non-synchronous actions that would make
|
||||
# non-owning StringRef dangle (rx_buf_ reused after delay)
|
||||
# non-owning args (StringRef, const FixedVector&) dangle once the
|
||||
# rx_buf_ is reused after a delay/wait_until/script.wait/etc. The
|
||||
# FixedVector references would also fail to compile because they
|
||||
# are non-copyable and DelayAction captures args by value.
|
||||
has_non_synchronous = automation.has_non_synchronous_actions(
|
||||
conf.get(CONF_THEN, [])
|
||||
)
|
||||
|
||||
service_arg_names: list[str] = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
# Fall back to std::string for string args if non-synchronous actions exist
|
||||
if has_non_synchronous and native is cg.StringRef:
|
||||
native = cg.std_string
|
||||
if has_non_synchronous and var_ in SERVICE_ARG_FALLBACK_TYPES:
|
||||
native = SERVICE_ARG_FALLBACK_TYPES[var_]
|
||||
else:
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
service_template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
|
||||
@@ -1025,13 +1025,6 @@ message CameraImageRequest {
|
||||
bool stream = 2;
|
||||
}
|
||||
|
||||
// ==================== TEMPERATURE UNIT ====================
|
||||
enum TemperatureUnit {
|
||||
TEMPERATURE_UNIT_CELSIUS = 0;
|
||||
TEMPERATURE_UNIT_FAHRENHEIT = 1;
|
||||
TEMPERATURE_UNIT_KELVIN = 2;
|
||||
}
|
||||
|
||||
// ==================== CLIMATE ====================
|
||||
enum ClimateMode {
|
||||
CLIMATE_MODE_OFF = 0;
|
||||
@@ -1117,7 +1110,6 @@ message ListEntitiesClimateResponse {
|
||||
float visual_max_humidity = 25;
|
||||
uint32 device_id = 26 [(field_ifdef) = "USE_DEVICES"];
|
||||
uint32 feature_flags = 27;
|
||||
TemperatureUnit temperature_unit = 28;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -1211,7 +1203,6 @@ message ListEntitiesWaterHeaterResponse {
|
||||
repeated WaterHeaterMode supported_modes = 11 [(container_pointer_no_template) = "water_heater::WaterHeaterModeMask"];
|
||||
// Bitmask of WaterHeaterFeature flags
|
||||
uint32 supported_features = 12;
|
||||
TemperatureUnit temperature_unit = 13;
|
||||
}
|
||||
|
||||
message WaterHeaterStateResponse {
|
||||
@@ -1419,8 +1410,6 @@ enum LockState {
|
||||
LOCK_STATE_JAMMED = 3;
|
||||
LOCK_STATE_LOCKING = 4;
|
||||
LOCK_STATE_UNLOCKING = 5;
|
||||
LOCK_STATE_OPENING = 6;
|
||||
LOCK_STATE_OPEN = 7;
|
||||
}
|
||||
enum LockCommand {
|
||||
LOCK_UNLOCK = 0;
|
||||
@@ -1639,7 +1628,7 @@ message BluetoothLEAdvertisementResponse {
|
||||
|
||||
message BluetoothLERawAdvertisement {
|
||||
option (inline_encode) = true;
|
||||
uint64 address = 1 [(force) = true, (mac_address) = true];
|
||||
uint64 address = 1 [(force) = true];
|
||||
sint32 rssi = 2 [(force) = true];
|
||||
uint32 address_type = 3 [(max_value) = 4];
|
||||
|
||||
@@ -2555,50 +2544,27 @@ message ListEntitiesInfraredResponse {
|
||||
message InfraredRFTransmitRawTimingsRequest {
|
||||
option (id) = 136;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_IR_RF || USE_RADIO_FREQUENCY";
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance
|
||||
uint32 carrier_frequency = 3; // Carrier frequency in Hz
|
||||
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the transmitter instance
|
||||
uint32 carrier_frequency = 3; // Carrier frequency in Hz
|
||||
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
|
||||
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
|
||||
uint32 modulation = 6; // RadioFrequencyModulation enum value (0 = OOK; ignored for IR entities)
|
||||
}
|
||||
|
||||
// Event message for received infrared/RF data
|
||||
message InfraredRFReceiveEvent {
|
||||
option (id) = 137;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_IR_RF || USE_RADIO_FREQUENCY";
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
option (no_delay) = true;
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance
|
||||
fixed32 key = 2 [(force) = true]; // Key identifying the receiver instance
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
}
|
||||
|
||||
// ==================== RADIO FREQUENCY ====================
|
||||
|
||||
// Lists available radio frequency entity instances
|
||||
message ListEntitiesRadioFrequencyResponse {
|
||||
option (id) = 148;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_RADIO_FREQUENCY";
|
||||
|
||||
string object_id = 1 [(max_data_length) = 120, (force) = true];
|
||||
fixed32 key = 2 [(force) = true];
|
||||
string name = 3 [(max_data_length) = 120, (force) = true];
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON", (max_data_length) = 63];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
uint32 capabilities = 8; // Bitmask of RadioFrequencyCapabilityFlags: bit 0 = transmitter, bit 1 = receiver
|
||||
uint32 frequency_min = 9; // Minimum tunable frequency in Hz; if min == max (non-zero): fixed frequency; 0 = unspecified
|
||||
uint32 frequency_max = 10; // Maximum tunable frequency in Hz; 0 = unspecified
|
||||
uint32 supported_modulations = 11; // Bitmask of supported RadioFrequencyModulation values (bit N = modulation N supported)
|
||||
}
|
||||
|
||||
// ==================== SERIAL PROXY ====================
|
||||
|
||||
enum SerialProxyParity {
|
||||
|
||||
@@ -49,9 +49,6 @@
|
||||
#ifdef USE_INFRARED
|
||||
#include "esphome/components/infrared/infrared.h"
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
#include "esphome/components/radio_frequency/radio_frequency.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -103,12 +100,6 @@ static const int CAMERA_STOP_STREAM = 5000;
|
||||
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id); \
|
||||
if ((entity_var) == nullptr) \
|
||||
return;
|
||||
|
||||
// Helper macro for multi-entity dispatch: looks up an entity by key and device_id without early return or make_call().
|
||||
// Use when multiple entity types must be checked in sequence (at most one will match).
|
||||
#define ENTITY_COMMAND_LOOKUP(entity_type, entity_var, getter_name) \
|
||||
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key, msg.device_id)
|
||||
|
||||
#else // No device support, use simpler macros
|
||||
// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call
|
||||
// object
|
||||
@@ -124,12 +115,6 @@ static const int CAMERA_STOP_STREAM = 5000;
|
||||
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
|
||||
if ((entity_var) == nullptr) \
|
||||
return;
|
||||
|
||||
// Helper macro for multi-entity dispatch: looks up an entity by key without early return or make_call().
|
||||
// Use when multiple entity types must be checked in sequence (at most one will match).
|
||||
#define ENTITY_COMMAND_LOOKUP(entity_type, entity_var, getter_name) \
|
||||
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key)
|
||||
|
||||
#endif // USE_DEVICES
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) : parent_(parent) {
|
||||
@@ -1486,36 +1471,19 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
void APIConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
|
||||
// Dispatch by key: infrared entities are checked first, then radio frequency entities.
|
||||
// The key is unique across all entity instances on a device, so at most one lookup will succeed.
|
||||
// TODO: When RF is implemented, add a field to the message to distinguish IR vs RF
|
||||
// and dispatch to the appropriate entity type based on that field.
|
||||
#ifdef USE_INFRARED
|
||||
ENTITY_COMMAND_LOOKUP(infrared::Infrared, infrared, infrared);
|
||||
if (infrared != nullptr) {
|
||||
auto call = infrared->make_call();
|
||||
call.set_carrier_frequency(msg.carrier_frequency);
|
||||
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
|
||||
call.set_repeat_count(msg.repeat_count);
|
||||
call.perform();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
ENTITY_COMMAND_LOOKUP(radio_frequency::RadioFrequency, radio_frequency, radio_frequency);
|
||||
if (radio_frequency != nullptr) {
|
||||
auto call = radio_frequency->make_call();
|
||||
call.set_frequency(msg.carrier_frequency);
|
||||
call.set_modulation(static_cast<radio_frequency::RadioFrequencyModulation>(msg.modulation));
|
||||
call.set_repeat_count(msg.repeat_count);
|
||||
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
|
||||
call.perform();
|
||||
}
|
||||
ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared)
|
||||
call.set_carrier_frequency(msg.carrier_frequency);
|
||||
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
|
||||
call.set_repeat_count(msg.repeat_count);
|
||||
call.perform();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) { this->send_message(msg); }
|
||||
#endif
|
||||
|
||||
@@ -1612,19 +1580,6 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
uint16_t APIConnection::try_send_radio_frequency_info(EntityBase *entity, APIConnection *conn,
|
||||
uint32_t remaining_size) {
|
||||
auto *rf = static_cast<radio_frequency::RadioFrequency *>(entity);
|
||||
ListEntitiesRadioFrequencyResponse msg;
|
||||
msg.capabilities = rf->get_capability_flags();
|
||||
msg.frequency_min = rf->get_traits().get_frequency_min_hz();
|
||||
msg.frequency_max = rf->get_traits().get_frequency_max_hz();
|
||||
msg.supported_modulations = rf->get_traits().get_supported_modulations();
|
||||
return fill_and_encode_entity_info(rf, msg, conn, remaining_size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
|
||||
@@ -2386,9 +2341,6 @@ uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item,
|
||||
#ifdef USE_INFRARED
|
||||
CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
CASE_INFO_ONLY(radio_frequency, ListEntitiesRadioFrequencyResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
CASE_INFO_ONLY(event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -223,7 +223,7 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg);
|
||||
#endif
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg);
|
||||
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
|
||||
#endif
|
||||
@@ -612,9 +612,6 @@ class APIConnection final : public APIServerConnectionBase {
|
||||
#ifdef USE_INFRARED
|
||||
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
static uint16_t try_send_radio_frequency_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size);
|
||||
|
||||
@@ -195,10 +195,7 @@ class APIFrameHelper {
|
||||
}
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() const { return frame_footer_size_; }
|
||||
// Check if socket has buffered data ready to read.
|
||||
// Contract: callers must read until it would block (EAGAIN/EWOULDBLOCK)
|
||||
// or track that they stopped early and retry without this check.
|
||||
// See Socket::ready() for details.
|
||||
// Check if socket has data ready to read
|
||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||
// Release excess memory from internal buffers after initial sync
|
||||
void release_buffers() {
|
||||
|
||||
@@ -110,10 +110,4 @@ extend google.protobuf.FieldOptions {
|
||||
// length varint calculations and direct byte writes, since the length
|
||||
// varint is guaranteed to be 1 byte.
|
||||
optional uint32 max_data_length = 50018;
|
||||
|
||||
// mac_address: Field is a 48-bit MAC address stored in a uint64.
|
||||
// Emits encode_varint_raw_48bit which has a 7-byte fast path that avoids
|
||||
// the per-byte loop when the upper bits are non-zero (the common case
|
||||
// for real MAC addresses, since OUIs occupy the top 24 bits).
|
||||
optional bool mac_address = 50019 [default=false];
|
||||
}
|
||||
|
||||
@@ -1439,7 +1439,6 @@ uint8_t *ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCO
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 26, this->device_id);
|
||||
#endif
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 27, this->feature_flags);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 28, static_cast<uint32_t>(this->temperature_unit));
|
||||
return pos;
|
||||
}
|
||||
uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
@@ -1489,7 +1488,6 @@ uint32_t ListEntitiesClimateResponse::calculate_size() const {
|
||||
size += ProtoSize::calc_uint32(2, this->device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_uint32(2, this->feature_flags);
|
||||
size += this->temperature_unit ? 3 : 0;
|
||||
return size;
|
||||
}
|
||||
uint8_t *ClimateStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
@@ -1647,7 +1645,6 @@ uint8_t *ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer PROTO_
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, static_cast<uint32_t>(it), true);
|
||||
}
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 12, this->supported_features);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 13, static_cast<uint32_t>(this->temperature_unit));
|
||||
return pos;
|
||||
}
|
||||
uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
@@ -1670,7 +1667,6 @@ uint32_t ListEntitiesWaterHeaterResponse::calculate_size() const {
|
||||
size += this->supported_modes->size() * 2;
|
||||
}
|
||||
size += ProtoSize::calc_uint32(1, this->supported_features);
|
||||
size += this->temperature_unit ? 2 : 0;
|
||||
return size;
|
||||
}
|
||||
uint8_t *WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
@@ -2352,7 +2348,7 @@ BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCO
|
||||
uint8_t *len_pos = pos;
|
||||
ProtoEncode::reserve_byte(pos PROTO_ENCODE_DEBUG_ARG);
|
||||
ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 8);
|
||||
ProtoEncode::encode_varint_raw_48bit(pos PROTO_ENCODE_DEBUG_ARG, sub_msg.address);
|
||||
ProtoEncode::encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, sub_msg.address);
|
||||
ProtoEncode::write_raw_byte(pos PROTO_ENCODE_DEBUG_ARG, 16);
|
||||
ProtoEncode::encode_varint_raw_short(pos PROTO_ENCODE_DEBUG_ARG, encode_zigzag32(sub_msg.rssi));
|
||||
if (sub_msg.address_type) {
|
||||
@@ -2373,7 +2369,7 @@ BluetoothLERawAdvertisementsResponse::calculate_size() const {
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
auto &sub_msg = this->advertisements[i];
|
||||
size += 2;
|
||||
size += ProtoSize::calc_uint64_48bit_force(1, sub_msg.address);
|
||||
size += ProtoSize::calc_uint64_force(1, sub_msg.address);
|
||||
size += ProtoSize::calc_sint32_force(1, sub_msg.rssi);
|
||||
size += sub_msg.address_type ? 2 : 0;
|
||||
size += 2 + sub_msg.data_len;
|
||||
@@ -3865,7 +3861,7 @@ uint32_t ListEntitiesInfraredResponse::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
|
||||
switch (field_id) {
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3879,9 +3875,6 @@ bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, proto
|
||||
case 4:
|
||||
this->repeat_count = value;
|
||||
break;
|
||||
case 6:
|
||||
this->modulation = value;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3935,46 +3928,6 @@ uint32_t InfraredRFReceiveEvent::calculate_size() const {
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
uint8_t *ListEntitiesRadioFrequencyResponse::encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const {
|
||||
uint8_t *__restrict__ pos = buffer.get_pos();
|
||||
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 10, this->object_id);
|
||||
ProtoEncode::write_tag_and_fixed32(pos PROTO_ENCODE_DEBUG_ARG, 21, this->key);
|
||||
ProtoEncode::encode_short_string_force(pos PROTO_ENCODE_DEBUG_ARG, 26, this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
ProtoEncode::encode_string(pos PROTO_ENCODE_DEBUG_ARG, 4, this->icon);
|
||||
#endif
|
||||
ProtoEncode::encode_bool(pos PROTO_ENCODE_DEBUG_ARG, 5, this->disabled_by_default);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 6, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 7, this->device_id);
|
||||
#endif
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 8, this->capabilities);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 9, this->frequency_min);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 10, this->frequency_max);
|
||||
ProtoEncode::encode_uint32(pos PROTO_ENCODE_DEBUG_ARG, 11, this->supported_modulations);
|
||||
return pos;
|
||||
}
|
||||
uint32_t ListEntitiesRadioFrequencyResponse::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
size += 2 + this->object_id.size();
|
||||
size += 5;
|
||||
size += 2 + this->name.size();
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size += !this->icon.empty() ? 2 + this->icon.size() : 0;
|
||||
#endif
|
||||
size += ProtoSize::calc_bool(1, this->disabled_by_default);
|
||||
size += this->entity_category ? 2 : 0;
|
||||
#ifdef USE_DEVICES
|
||||
size += ProtoSize::calc_uint32(1, this->device_id);
|
||||
#endif
|
||||
size += ProtoSize::calc_uint32(1, this->capabilities);
|
||||
size += ProtoSize::calc_uint32(1, this->frequency_min);
|
||||
size += ProtoSize::calc_uint32(1, this->frequency_max);
|
||||
size += ProtoSize::calc_uint32(1, this->supported_modulations);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
bool SerialProxyConfigureRequest::decode_varint(uint32_t field_id, proto_varint_value_t value) {
|
||||
switch (field_id) {
|
||||
|
||||
@@ -92,11 +92,6 @@ enum SupportsResponseType : uint32_t {
|
||||
SUPPORTS_RESPONSE_STATUS = 100,
|
||||
};
|
||||
#endif
|
||||
enum TemperatureUnit : uint32_t {
|
||||
TEMPERATURE_UNIT_CELSIUS = 0,
|
||||
TEMPERATURE_UNIT_FAHRENHEIT = 1,
|
||||
TEMPERATURE_UNIT_KELVIN = 2,
|
||||
};
|
||||
#ifdef USE_CLIMATE
|
||||
enum ClimateMode : uint32_t {
|
||||
CLIMATE_MODE_OFF = 0,
|
||||
@@ -181,8 +176,6 @@ enum LockState : uint32_t {
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5,
|
||||
LOCK_STATE_OPENING = 6,
|
||||
LOCK_STATE_OPEN = 7,
|
||||
};
|
||||
enum LockCommand : uint32_t {
|
||||
LOCK_UNLOCK = 0,
|
||||
@@ -1379,7 +1372,7 @@ class CameraImageRequest final : public ProtoDecodableMessage {
|
||||
class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 46;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 153;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 150;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("list_entities_climate_response"); }
|
||||
#endif
|
||||
@@ -1401,7 +1394,6 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||
float visual_min_humidity{0.0f};
|
||||
float visual_max_humidity{0.0f};
|
||||
uint32_t feature_flags{0};
|
||||
enums::TemperatureUnit temperature_unit{};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1479,7 +1471,7 @@ class ClimateCommandRequest final : public CommandProtoMessage {
|
||||
class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 132;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 65;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 63;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("list_entities_water_heater_response"); }
|
||||
#endif
|
||||
@@ -1488,7 +1480,6 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
float target_temperature_step{0.0f};
|
||||
const water_heater::WaterHeaterModeMask *supported_modes{};
|
||||
uint32_t supported_features{0};
|
||||
enums::TemperatureUnit temperature_unit{};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -3063,11 +3054,11 @@ class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 136;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 224;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 220;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("infrared_rf_transmit_raw_timings_request"); }
|
||||
#endif
|
||||
@@ -3080,7 +3071,6 @@ class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
|
||||
const uint8_t *timings_data_{nullptr};
|
||||
uint16_t timings_length_{0};
|
||||
uint16_t timings_count_{0};
|
||||
uint32_t modulation{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
@@ -3111,27 +3101,6 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
class ListEntitiesRadioFrequencyResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 148;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 56;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const LogString *message_name() const override { return LOG_STR("list_entities_radio_frequency_response"); }
|
||||
#endif
|
||||
uint32_t capabilities{0};
|
||||
uint32_t frequency_min{0};
|
||||
uint32_t frequency_max{0};
|
||||
uint32_t supported_modulations{0};
|
||||
uint8_t *encode(ProtoWriteBuffer &buffer PROTO_ENCODE_DEBUG_PARAM) const;
|
||||
uint32_t calculate_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
class SerialProxyConfigureRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
|
||||
@@ -297,18 +297,6 @@ template<> const char *proto_enum_to_string<enums::SupportsResponseType>(enums::
|
||||
}
|
||||
}
|
||||
#endif
|
||||
template<> const char *proto_enum_to_string<enums::TemperatureUnit>(enums::TemperatureUnit value) {
|
||||
switch (value) {
|
||||
case enums::TEMPERATURE_UNIT_CELSIUS:
|
||||
return ESPHOME_PSTR("TEMPERATURE_UNIT_CELSIUS");
|
||||
case enums::TEMPERATURE_UNIT_FAHRENHEIT:
|
||||
return ESPHOME_PSTR("TEMPERATURE_UNIT_FAHRENHEIT");
|
||||
case enums::TEMPERATURE_UNIT_KELVIN:
|
||||
return ESPHOME_PSTR("TEMPERATURE_UNIT_KELVIN");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
#ifdef USE_CLIMATE
|
||||
template<> const char *proto_enum_to_string<enums::ClimateMode>(enums::ClimateMode value) {
|
||||
switch (value) {
|
||||
@@ -487,10 +475,6 @@ template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState v
|
||||
return ESPHOME_PSTR("LOCK_STATE_LOCKING");
|
||||
case enums::LOCK_STATE_UNLOCKING:
|
||||
return ESPHOME_PSTR("LOCK_STATE_UNLOCKING");
|
||||
case enums::LOCK_STATE_OPENING:
|
||||
return ESPHOME_PSTR("LOCK_STATE_OPENING");
|
||||
case enums::LOCK_STATE_OPEN:
|
||||
return ESPHOME_PSTR("LOCK_STATE_OPEN");
|
||||
default:
|
||||
return ESPHOME_PSTR("UNKNOWN");
|
||||
}
|
||||
@@ -1555,7 +1539,6 @@ const char *ListEntitiesClimateResponse::dump_to(DumpBuffer &out) const {
|
||||
dump_field(out, ESPHOME_PSTR("device_id"), this->device_id);
|
||||
#endif
|
||||
dump_field(out, ESPHOME_PSTR("feature_flags"), this->feature_flags);
|
||||
dump_field(out, ESPHOME_PSTR("temperature_unit"), static_cast<enums::TemperatureUnit>(this->temperature_unit));
|
||||
return out.c_str();
|
||||
}
|
||||
const char *ClimateStateResponse::dump_to(DumpBuffer &out) const {
|
||||
@@ -1629,7 +1612,6 @@ const char *ListEntitiesWaterHeaterResponse::dump_to(DumpBuffer &out) const {
|
||||
dump_field(out, ESPHOME_PSTR("supported_modes"), static_cast<enums::WaterHeaterMode>(it), 4);
|
||||
}
|
||||
dump_field(out, ESPHOME_PSTR("supported_features"), this->supported_features);
|
||||
dump_field(out, ESPHOME_PSTR("temperature_unit"), static_cast<enums::TemperatureUnit>(this->temperature_unit));
|
||||
return out.c_str();
|
||||
}
|
||||
const char *WaterHeaterStateResponse::dump_to(DumpBuffer &out) const {
|
||||
@@ -2594,7 +2576,7 @@ const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const {
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
const char *InfraredRFTransmitRawTimingsRequest::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("InfraredRFTransmitRawTimingsRequest"));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -2609,7 +2591,6 @@ const char *InfraredRFTransmitRawTimingsRequest::dump_to(DumpBuffer &out) const
|
||||
out.append_p(ESPHOME_PSTR(" values, "));
|
||||
append_uint(out, this->timings_length_);
|
||||
out.append_p(ESPHOME_PSTR(" bytes]\n"));
|
||||
dump_field(out, ESPHOME_PSTR("modulation"), this->modulation);
|
||||
return out.c_str();
|
||||
}
|
||||
const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const {
|
||||
@@ -2624,27 +2605,6 @@ const char *InfraredRFReceiveEvent::dump_to(DumpBuffer &out) const {
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
const char *ListEntitiesRadioFrequencyResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("ListEntitiesRadioFrequencyResponse"));
|
||||
dump_field(out, ESPHOME_PSTR("object_id"), this->object_id);
|
||||
dump_field(out, ESPHOME_PSTR("key"), this->key);
|
||||
dump_field(out, ESPHOME_PSTR("name"), this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, ESPHOME_PSTR("icon"), this->icon);
|
||||
#endif
|
||||
dump_field(out, ESPHOME_PSTR("disabled_by_default"), this->disabled_by_default);
|
||||
dump_field(out, ESPHOME_PSTR("entity_category"), static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, ESPHOME_PSTR("device_id"), this->device_id);
|
||||
#endif
|
||||
dump_field(out, ESPHOME_PSTR("capabilities"), this->capabilities);
|
||||
dump_field(out, ESPHOME_PSTR("frequency_min"), this->frequency_min);
|
||||
dump_field(out, ESPHOME_PSTR("frequency_max"), this->frequency_max);
|
||||
dump_field(out, ESPHOME_PSTR("supported_modulations"), this->supported_modulations);
|
||||
return out.c_str();
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SERIAL_PROXY
|
||||
const char *SerialProxyConfigureRequest::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, ESPHOME_PSTR("SerialProxyConfigureRequest"));
|
||||
|
||||
@@ -21,7 +21,6 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_API
|
||||
void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements
|
||||
switch (msg_type) {
|
||||
@@ -626,7 +625,7 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: {
|
||||
InfraredRFTransmitRawTimingsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
@@ -707,6 +706,5 @@ void APIConnection::read_message_(uint32_t msg_size, uint32_t msg_type, const ui
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // USE_API
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -211,7 +211,7 @@ class APIServerConnectionBase {
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
|
||||
@@ -30,11 +30,6 @@ APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-c
|
||||
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
|
||||
// Custom deleter defined here so `delete` sees the complete APIConnection type.
|
||||
// This prevents libc++ from emitting an "incomplete type" error when other
|
||||
// translation units only have the forward declaration of APIConnection.
|
||||
void APIServer::APIConnectionDeleter::operator()(APIConnection *p) const { delete p; }
|
||||
|
||||
void APIServer::socket_failed_(const LogString *msg) {
|
||||
ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
|
||||
this->destroy_socket_();
|
||||
@@ -123,7 +118,7 @@ void APIServer::loop() {
|
||||
this->accept_new_connections_();
|
||||
}
|
||||
|
||||
if (this->api_connection_count_ == 0) {
|
||||
if (this->clients_.empty()) {
|
||||
// Check reboot timeout - done in loop to avoid scheduler heap churn
|
||||
// (cancelled scheduler items sit in heap memory until their scheduled time)
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
@@ -140,15 +135,15 @@ void APIServer::loop() {
|
||||
// Check network connectivity once for all clients
|
||||
if (!network::is_connected()) {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->active_clients()) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
|
||||
uint8_t client_index = 0;
|
||||
while (client_index < this->api_connection_count_) {
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
// Common case: process active client
|
||||
@@ -166,7 +161,7 @@ void APIServer::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void APIServer::remove_client_(uint8_t client_index) {
|
||||
void APIServer::remove_client_(size_t client_index) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
@@ -184,17 +179,14 @@ void APIServer::remove_client_(uint8_t client_index) {
|
||||
// Close socket now (was deferred from on_fatal_error to allow getpeername)
|
||||
client->helper_->close();
|
||||
|
||||
// Swap-and-reset: move the removed client to the trailing slot and null it out so slots
|
||||
// [api_connection_count_, N) remain nullptr.
|
||||
const uint8_t last_index = this->api_connection_count_ - 1;
|
||||
if (client_index < last_index) {
|
||||
std::swap(this->clients_[client_index], this->clients_[last_index]);
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
}
|
||||
this->clients_[last_index].reset();
|
||||
this->api_connection_count_--;
|
||||
this->clients_.pop_back();
|
||||
|
||||
// Last client disconnected - set warning and start tracking for reboot timeout
|
||||
if (this->api_connection_count_ == 0 && this->reboot_timeout_ != 0) {
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning(LOG_STR("waiting for client connection"));
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
@@ -218,8 +210,8 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() {
|
||||
sock->getpeername_to(peername);
|
||||
|
||||
// Check if we're at the connection limit
|
||||
if (this->api_connection_count_ >= MAX_API_CONNECTIONS) {
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", MAX_API_CONNECTIONS, peername);
|
||||
if (this->clients_.size() >= this->max_connections_) {
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
|
||||
// Immediately close - socket destructor will handle cleanup
|
||||
sock.reset();
|
||||
continue;
|
||||
@@ -228,11 +220,11 @@ void __attribute__((flatten)) APIServer::accept_new_connections_() {
|
||||
ESP_LOGD(TAG, "Accept %s", peername);
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_[this->api_connection_count_++].reset(conn);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
|
||||
// First client connected - clear warning and update timestamp
|
||||
if (this->api_connection_count_ == 1 && this->reboot_timeout_ != 0) {
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
@@ -245,7 +237,7 @@ void APIServer::dump_config() {
|
||||
" Address: %s:%u\n"
|
||||
" Listen backlog: %u\n"
|
||||
" Max connections: %u",
|
||||
network::get_use_address(), this->port_, this->listen_backlog_, MAX_API_CONNECTIONS);
|
||||
network::get_use_address(), this->port_, this->listen_backlog_, this->max_connections_);
|
||||
#ifdef USE_API_NOISE
|
||||
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_.has_psk()));
|
||||
if (!this->noise_ctx_.has_psk()) {
|
||||
@@ -263,7 +255,7 @@ void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
return; \
|
||||
for (auto &c : this->active_clients()) { \
|
||||
for (auto &c : this->clients_) { \
|
||||
if (c->flags_.state_subscription) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
} \
|
||||
@@ -345,7 +337,7 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
||||
void APIServer::on_event(event::Event *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->flags_.state_subscription)
|
||||
c->send_event(obj);
|
||||
}
|
||||
@@ -357,7 +349,7 @@ void APIServer::on_event(event::Event *obj) {
|
||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (c->flags_.state_subscription)
|
||||
c->send_update_state(obj);
|
||||
}
|
||||
@@ -368,12 +360,12 @@ void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
void APIServer::on_zwave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||
// We could add code to manage a second subscription type, but, since this message type is
|
||||
// very infrequent and small, we simply send it to all clients
|
||||
for (auto &c : this->active_clients())
|
||||
for (auto &c : this->clients_)
|
||||
c->send_message(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key,
|
||||
const std::vector<int32_t> *timings) {
|
||||
InfraredRFReceiveEvent resp{};
|
||||
@@ -383,7 +375,7 @@ void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_
|
||||
resp.key = key;
|
||||
resp.timings = timings;
|
||||
|
||||
for (auto &c : this->active_clients())
|
||||
for (auto &c : this->clients_)
|
||||
c->send_infrared_rf_receive_event(resp);
|
||||
}
|
||||
#endif
|
||||
@@ -400,7 +392,7 @@ void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = bat
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void APIServer::send_homeassistant_action(const HomeassistantActionRequest &call) {
|
||||
for (auto &client : this->active_clients()) {
|
||||
for (auto &client : this->clients_) {
|
||||
client->send_homeassistant_action(call);
|
||||
}
|
||||
}
|
||||
@@ -540,7 +532,7 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
c->send_message(req);
|
||||
}
|
||||
@@ -591,7 +583,7 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
||||
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->active_clients()) {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->flags_.remove && client->is_authenticated()) {
|
||||
client->send_time_request();
|
||||
return; // Only request from one client to avoid clock conflicts
|
||||
@@ -601,8 +593,8 @@ void APIServer::request_time() {
|
||||
#endif
|
||||
|
||||
bool APIServer::is_connected_with_state_subscription() const {
|
||||
for (uint8_t i = 0; i < this->api_connection_count_; i++) {
|
||||
if (this->clients_[i]->flags_.state_subscription) {
|
||||
for (const auto &client : this->clients_) {
|
||||
if (client->flags_.state_subscription) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -617,7 +609,7 @@ void APIServer::on_log(uint8_t level, const char *tag, const char *message, size
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
@@ -626,7 +618,7 @@ void APIServer::on_log(uint8_t level, const char *tag, const char *message, size
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
void APIServer::on_camera_image(const std::shared_ptr<camera::CameraImage> &image) {
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove)
|
||||
c->set_camera_state(image);
|
||||
}
|
||||
@@ -643,7 +635,7 @@ void APIServer::on_shutdown() {
|
||||
this->batch_delay_ = 5;
|
||||
|
||||
// Send disconnect requests to all connected clients
|
||||
for (auto &c : this->active_clients()) {
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
if (!c->send_message(req)) {
|
||||
// If we can't send the disconnect request directly (tx_buffer full),
|
||||
@@ -661,7 +653,7 @@ bool APIServer::teardown() {
|
||||
this->loop();
|
||||
|
||||
// Return true only when all clients have been torn down
|
||||
return this->api_connection_count_ == 0;
|
||||
return this->clients_.empty();
|
||||
}
|
||||
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
#include "esphome/components/camera/camera.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -65,6 +63,7 @@ class APIServer final : public Component,
|
||||
void set_batch_delay(uint16_t batch_delay);
|
||||
uint16_t get_batch_delay() const { return batch_delay_; }
|
||||
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
|
||||
void set_max_connections(uint8_t max_connections) { this->max_connections_ = max_connections; }
|
||||
|
||||
// Get reference to shared buffer for API connections
|
||||
APIBuffer &get_shared_buffer_ref() { return shared_write_buffer_; }
|
||||
@@ -183,36 +182,13 @@ class APIServer final : public Component,
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
|
||||
#endif
|
||||
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
||||
#ifdef USE_IR_RF
|
||||
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
|
||||
#endif
|
||||
|
||||
bool is_connected() const { return this->api_connection_count_ != 0; }
|
||||
bool is_connected() const { return !this->clients_.empty(); }
|
||||
bool is_connected_with_state_subscription() const;
|
||||
|
||||
// Range-for view over the populated slice [0, api_connection_count_). Read-only with respect
|
||||
// to ownership — callers get `const unique_ptr&` so they can invoke non-const methods on the
|
||||
// APIConnection but cannot reset/move the slot and break the count invariant.
|
||||
// Custom deleter is defined out-of-line in api_server.cpp so libc++ does not
|
||||
// eagerly instantiate `delete static_cast<APIConnection *>(p)` here, where
|
||||
// only the forward declaration of APIConnection is visible (incomplete type).
|
||||
struct APIConnectionDeleter {
|
||||
void operator()(APIConnection *p) const;
|
||||
};
|
||||
using APIConnectionPtr = std::unique_ptr<APIConnection, APIConnectionDeleter>;
|
||||
class ActiveClientsView {
|
||||
const APIConnectionPtr *begin_;
|
||||
const APIConnectionPtr *end_;
|
||||
|
||||
public:
|
||||
ActiveClientsView(const APIConnectionPtr *b, const APIConnectionPtr *e) : begin_(b), end_(e) {}
|
||||
const APIConnectionPtr *begin() const { return this->begin_; }
|
||||
const APIConnectionPtr *end() const { return this->end_; }
|
||||
};
|
||||
ActiveClientsView active_clients() const {
|
||||
return {this->clients_.data(), this->clients_.data() + this->api_connection_count_};
|
||||
}
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
struct HomeAssistantStateSubscription {
|
||||
const char *entity_id; // Pointer to flash (internal) or heap (external)
|
||||
@@ -258,8 +234,8 @@ class APIServer final : public Component,
|
||||
protected:
|
||||
// Accept incoming socket connections. Only called when socket has pending connections.
|
||||
void __attribute__((noinline)) accept_new_connections_();
|
||||
// Remove a disconnected client by index. Swaps with the last populated slot and resets it.
|
||||
void __attribute__((noinline)) remove_client_(uint8_t client_index);
|
||||
// Remove a disconnected client by index. Swaps with last element and pops.
|
||||
void __attribute__((noinline)) remove_client_(size_t client_index);
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||
@@ -297,9 +273,8 @@ class APIServer final : public Component,
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t last_connected_{0};
|
||||
|
||||
// Slots [0, api_connection_count_) are populated; trailing slots are always nullptr.
|
||||
std::array<APIConnectionPtr, MAX_API_CONNECTIONS> clients_{};
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
// Shared proto write buffer for all connections.
|
||||
// Not pre-allocated: all send paths call prepare_first_message_buffer() which
|
||||
// reserves the exact needed size. Pre-allocating here would cause heap fragmentation
|
||||
@@ -334,10 +309,10 @@ class APIServer final : public Component,
|
||||
uint16_t port_{6053};
|
||||
uint16_t batch_delay_{100};
|
||||
// Connection limits - these defaults will be overridden by config values
|
||||
// from cv.SplitDefault in __init__.py which sets platform-specific defaults.
|
||||
// from cv.SplitDefault in __init__.py which sets platform-specific defaults
|
||||
uint8_t listen_backlog_{4};
|
||||
uint8_t max_connections_{8};
|
||||
bool shutting_down_ = false;
|
||||
uint8_t api_connection_count_{0};
|
||||
// 7 bytes used, 1 byte padding
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
|
||||
@@ -20,6 +20,7 @@ import contextlib
|
||||
from esphome.const import CONF_KEY, CONF_PORT, __version__
|
||||
from esphome.core import CORE
|
||||
from esphome.platformio_api import process_stacktrace
|
||||
from esphome.util import safe_print
|
||||
|
||||
from . import CONF_ENCRYPTION
|
||||
|
||||
@@ -60,7 +61,6 @@ async def async_run_logs(
|
||||
noise_psk=noise_psk,
|
||||
addresses=addresses, # Pass all addresses for automatic retry
|
||||
)
|
||||
dashboard = CORE.dashboard
|
||||
backtrace_state = False
|
||||
|
||||
# Try platform-specific stacktrace handler first, fall back to generic
|
||||
@@ -82,7 +82,11 @@ async def async_run_logs(
|
||||
f"[{time_.hour:02}:{time_.minute:02}:{time_.second:02}.{nanoseconds:03}]"
|
||||
)
|
||||
for parsed_msg in parse_log_message(text, timestamp):
|
||||
print(parsed_msg.replace("\033", "\\033") if dashboard else parsed_msg)
|
||||
# safe_print handles the dashboard \033 escaping and falls back
|
||||
# to backslashreplace encoding on stdouts that can't represent
|
||||
# the wifi signal-bar block characters (Windows redirected
|
||||
# cp1252 pipe).
|
||||
safe_print(parsed_msg)
|
||||
for raw_line in text.splitlines():
|
||||
if platform_process_stacktrace:
|
||||
backtrace_state = platform_process_stacktrace(
|
||||
@@ -93,24 +97,7 @@ async def async_run_logs(
|
||||
config, raw_line, backtrace_state=backtrace_state
|
||||
)
|
||||
|
||||
# Safe to fall back to plaintext here only for this diagnostics use
|
||||
# case: the stream is one-way from device to client, and this code
|
||||
# never accepts commands or acts on any message the device sends.
|
||||
# An on-path attacker could still both inject fabricated log lines
|
||||
# and passively read the device's log output (and any state data
|
||||
# delivered when subscribe_states is enabled), so this does lose
|
||||
# confidentiality as well as authentication/integrity. That tradeoff
|
||||
# is acceptable for operator-visible logs, which aioesphomeapi also
|
||||
# warns may come from an unverified device. Never mirror this opt-in
|
||||
# for any connection that sends data to the device or uses Home
|
||||
# Assistant actions.
|
||||
stop = await async_run(
|
||||
cli,
|
||||
on_log,
|
||||
name=name,
|
||||
subscribe_states=subscribe_states,
|
||||
allow_plaintext_fallback=True,
|
||||
)
|
||||
stop = await async_run(cli, on_log, name=name, subscribe_states=subscribe_states)
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
|
||||
@@ -79,9 +79,6 @@ LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWater
|
||||
#ifdef USE_INFRARED
|
||||
LIST_ENTITIES_HANDLER(infrared, infrared::Infrared, ListEntitiesInfraredResponse)
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
LIST_ENTITIES_HANDLER(radio_frequency, radio_frequency::RadioFrequency, ListEntitiesRadioFrequencyResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -87,9 +87,6 @@ class ListEntitiesIterator final : public ComponentIterator {
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *entity) override;
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
bool on_radio_frequency(radio_frequency::RadioFrequency *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
|
||||
@@ -342,32 +342,6 @@ class ProtoEncode {
|
||||
}
|
||||
encode_varint_raw_loop(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
/// Encode a 48-bit MAC address (stored in a uint64) as varint.
|
||||
/// Real MAC addresses occupy the full 48 bits (OUI in upper 24), so the
|
||||
/// fast path -- any non-zero bit in the top 6 of 48 -- emits exactly 7 bytes
|
||||
/// with no per-byte branch. Falls back to the general loop otherwise.
|
||||
/// Caller must guarantee value fits in 48 bits (checked in debug builds).
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_varint_raw_48bit(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint64_t value) {
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
assert(value < (1ULL << (MAC_ADDRESS_SIZE * 8)) && "encode_varint_raw_48bit: value exceeds 48 bits");
|
||||
#endif
|
||||
// 7-byte varint holds 49 bits (7 * 7), so a 48-bit value needs all 7 bytes
|
||||
// whenever bit 42 or higher is set (i.e. value >= 1 << (48 - 6)).
|
||||
if (value >= (1ULL << (MAC_ADDRESS_SIZE * 8 - 6))) [[likely]] {
|
||||
PROTO_ENCODE_CHECK_BOUNDS(pos, 7);
|
||||
pos[0] = static_cast<uint8_t>(value | 0x80);
|
||||
pos[1] = static_cast<uint8_t>((value >> 7) | 0x80);
|
||||
pos[2] = static_cast<uint8_t>((value >> 14) | 0x80);
|
||||
pos[3] = static_cast<uint8_t>((value >> 21) | 0x80);
|
||||
pos[4] = static_cast<uint8_t>((value >> 28) | 0x80);
|
||||
pos[5] = static_cast<uint8_t>((value >> 35) | 0x80);
|
||||
pos[6] = static_cast<uint8_t>(value >> 42);
|
||||
pos += 7;
|
||||
return;
|
||||
}
|
||||
encode_varint_raw_64(pos PROTO_ENCODE_DEBUG_ARG, value);
|
||||
}
|
||||
static inline void ESPHOME_ALWAYS_INLINE encode_field_raw(uint8_t *__restrict__ &pos PROTO_ENCODE_DEBUG_PARAM,
|
||||
uint32_t field_id, uint32_t type) {
|
||||
encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, (field_id << 3) | type);
|
||||
@@ -843,14 +817,6 @@ class ProtoSize {
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_uint64_force(uint32_t field_id_size, uint64_t value) {
|
||||
return field_id_size + varint(value);
|
||||
}
|
||||
/// 48-bit MAC address variant: matches encode_varint_raw_48bit's fast path.
|
||||
/// When any of the top 6 of 48 bits is set the encoded varint is 7 bytes;
|
||||
/// otherwise fall back to the general size calculation.
|
||||
/// Caller must guarantee value fits in 48 bits (encoder asserts in debug).
|
||||
static constexpr inline uint32_t ESPHOME_ALWAYS_INLINE calc_uint64_48bit_force(uint32_t field_id_size,
|
||||
uint64_t value) {
|
||||
return field_id_size + (value >= (1ULL << (MAC_ADDRESS_SIZE * 8 - 6)) ? 7 : varint(value));
|
||||
}
|
||||
static constexpr uint32_t calc_length(uint32_t field_id_size, size_t len) {
|
||||
return len ? field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len) : 0;
|
||||
}
|
||||
|
||||
@@ -82,9 +82,6 @@ class InitialStateIterator final : public ComponentIterator {
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *infrared) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_RADIO_FREQUENCY
|
||||
bool on_radio_frequency(radio_frequency::RadioFrequency *radio_frequency) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
|
||||
@@ -83,7 +83,7 @@ def angle_to_position(value, min=-360, max=360):
|
||||
value = angle(min=min, max=max)(value)
|
||||
return (RESOLUTION + round(value * ANGLE_TO_POSITION)) % RESOLUTION
|
||||
except cv.Invalid as e:
|
||||
raise cv.Invalid(f"When using angle, {e.error_message}") from e
|
||||
raise cv.Invalid(f"When using angle, {e.error_message}")
|
||||
|
||||
|
||||
def percent_to_position(value):
|
||||
@@ -164,7 +164,7 @@ def has_valid_range_config():
|
||||
except cv.Invalid as e:
|
||||
raise cv.Invalid(
|
||||
f"The range between start and end position is invalid. It was was {range} but {e.error_message}"
|
||||
) from e
|
||||
)
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
include_builtin_idf_component,
|
||||
)
|
||||
from esphome.components.esp32 import add_idf_component, include_builtin_idf_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
||||
from esphome.core import CORE
|
||||
@@ -31,7 +27,6 @@ class AudioData:
|
||||
flac_support: bool = False
|
||||
mp3_support: bool = False
|
||||
opus_support: bool = False
|
||||
micro_decoder_support: bool = False
|
||||
|
||||
|
||||
def _get_data() -> AudioData:
|
||||
@@ -55,11 +50,6 @@ def request_opus_support() -> None:
|
||||
_get_data().opus_support = True
|
||||
|
||||
|
||||
def request_micro_decoder_support() -> None:
|
||||
"""Request micro-decoder library support for audio decoding."""
|
||||
_get_data().micro_decoder_support = True
|
||||
|
||||
|
||||
CONF_MIN_BITS_PER_SAMPLE = "min_bits_per_sample"
|
||||
CONF_MAX_BITS_PER_SAMPLE = "max_bits_per_sample"
|
||||
CONF_MIN_CHANNELS = "min_channels"
|
||||
@@ -218,19 +208,6 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
data = _get_data()
|
||||
|
||||
if data.micro_decoder_support:
|
||||
add_idf_component(name="esphome/micro-decoder", ref="0.2.0")
|
||||
|
||||
# All codecs are enabled by default in micro-decoder, so disable the ones that aren't requested to save flash
|
||||
if not data.flac_support:
|
||||
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_FLAC", False)
|
||||
if not data.mp3_support:
|
||||
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_MP3", False)
|
||||
if not data.opus_support:
|
||||
add_idf_sdkconfig_option("CONFIG_MICRO_DECODER_CODEC_OPUS", False)
|
||||
|
||||
# Legacy audio_decoder.cpp support defines and components
|
||||
if data.flac_support:
|
||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-flac", ref="0.1.1")
|
||||
|
||||
@@ -116,7 +116,7 @@ def read_audio_file_and_type(file_config: ConfigType) -> tuple[bytes, MockObj]:
|
||||
raise cv.Invalid(
|
||||
f"Unable to determine audio file type of '{path}'. "
|
||||
f"Try re-encoding the file into a supported format. Details: {e}"
|
||||
) from e
|
||||
)
|
||||
|
||||
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
|
||||
if file_type == "wav":
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#include "audio_http_media_source.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::audio_http {
|
||||
|
||||
static const char *const TAG = "audio_http_media_source";
|
||||
|
||||
// Decoder task / buffer tuning. Kept here as constants so the header stays free of magic numbers.
|
||||
static constexpr size_t DEFAULT_TRANSFER_BUFFER_SIZE = 8 * 1024; // Staging buffer between HTTP reader and decoder
|
||||
static constexpr uint32_t HTTP_TIMEOUT_MS = 5000; // HTTP connect/read timeout
|
||||
static constexpr uint32_t AUDIO_WRITE_TIMEOUT_MS = 50; // Max blocking time per on_audio_write() call
|
||||
static constexpr uint32_t READER_WRITE_TIMEOUT_MS = 50; // Max blocking time when writing into the ring buffer
|
||||
static constexpr uint8_t READER_TASK_PRIORITY = 2;
|
||||
static constexpr uint8_t DECODER_TASK_PRIORITY = 2;
|
||||
static constexpr size_t READER_TASK_STACK_SIZE = 4096;
|
||||
static constexpr size_t DECODER_TASK_STACK_SIZE = 5120;
|
||||
static constexpr uint32_t PAUSE_POLL_DELAY_MS = 20;
|
||||
static constexpr const char *const HTTP_URI_PREFIX = "http://";
|
||||
static constexpr const char *const HTTPS_URI_PREFIX = "https://";
|
||||
|
||||
void AudioHTTPMediaSource::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Audio HTTP Media Source:\n"
|
||||
" Buffer Size: %zu bytes\n"
|
||||
" Decoder Task Stack in PSRAM: %s",
|
||||
this->buffer_size_, YESNO(this->decoder_task_stack_in_psram_));
|
||||
}
|
||||
|
||||
void AudioHTTPMediaSource::setup() {
|
||||
this->disable_loop();
|
||||
|
||||
micro_decoder::DecoderConfig config;
|
||||
config.ring_buffer_size = this->buffer_size_;
|
||||
// Keep the transfer buffer smaller than the ring buffer so the reader can top up the ring
|
||||
// while the decoder is still draining it, instead of oscillating between empty and full.
|
||||
config.transfer_buffer_size = std::min(DEFAULT_TRANSFER_BUFFER_SIZE, this->buffer_size_ / 2);
|
||||
config.http_timeout_ms = HTTP_TIMEOUT_MS;
|
||||
config.audio_write_timeout_ms = AUDIO_WRITE_TIMEOUT_MS;
|
||||
config.reader_write_timeout_ms = READER_WRITE_TIMEOUT_MS;
|
||||
config.reader_priority = READER_TASK_PRIORITY;
|
||||
config.decoder_priority = DECODER_TASK_PRIORITY;
|
||||
config.reader_stack_size = READER_TASK_STACK_SIZE;
|
||||
config.decoder_stack_size = DECODER_TASK_STACK_SIZE;
|
||||
config.decoder_stack_in_psram = this->decoder_task_stack_in_psram_;
|
||||
|
||||
this->decoder_ = std::make_unique<micro_decoder::DecoderSource>(config);
|
||||
if (this->decoder_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate decoder");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->decoder_->set_listener(this); // We inherit from micro_decoder::DecoderListener
|
||||
}
|
||||
|
||||
void AudioHTTPMediaSource::loop() { this->decoder_->loop(); }
|
||||
|
||||
bool AudioHTTPMediaSource::can_handle(const std::string &uri) const {
|
||||
return uri.starts_with(HTTP_URI_PREFIX) || uri.starts_with(HTTPS_URI_PREFIX);
|
||||
}
|
||||
|
||||
// Called from the orchestrator's main loop, so no synchronization needed with loop()
|
||||
bool AudioHTTPMediaSource::play_uri(const std::string &uri) {
|
||||
if (!this->is_ready() || this->is_failed() || this->status_has_error() || !this->has_listener()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if source is already playing
|
||||
if (this->get_state() != media_source::MediaSourceState::IDLE) {
|
||||
ESP_LOGE(TAG, "Cannot play '%s': source is busy", uri.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate URI starts with "http://" or "https://"
|
||||
if (!uri.starts_with(HTTP_URI_PREFIX) && !uri.starts_with(HTTPS_URI_PREFIX)) {
|
||||
ESP_LOGE(TAG, "Invalid URI: '%s'", uri.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->decoder_->play_url(uri)) {
|
||||
this->pause_.store(false, std::memory_order_relaxed);
|
||||
this->enable_loop();
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Failed to start playback of '%s'", uri.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called from the orchestrator's main loop, so no synchronization needed with loop()
|
||||
void AudioHTTPMediaSource::handle_command(media_source::MediaSourceCommand command) {
|
||||
switch (command) {
|
||||
case media_source::MediaSourceCommand::STOP:
|
||||
this->decoder_->stop();
|
||||
break;
|
||||
case media_source::MediaSourceCommand::PAUSE:
|
||||
// Only valid while actively playing; ignoring from IDLE/ERROR/PAUSED prevents the state
|
||||
// machine from getting stuck in PAUSED when no playback is active (which would block the
|
||||
// next play_uri() call via its IDLE-state precondition).
|
||||
if (this->get_state() != media_source::MediaSourceState::PLAYING)
|
||||
break;
|
||||
// PAUSE does not stop the decoder task. Instead, on_audio_write() returns 0 and temporarily
|
||||
// yields, which fills the ring buffer and applies back pressure that effectively pauses both
|
||||
// the decoder and HTTP reader tasks.
|
||||
this->set_state_(media_source::MediaSourceState::PAUSED);
|
||||
this->pause_.store(true, std::memory_order_relaxed);
|
||||
break;
|
||||
case media_source::MediaSourceCommand::PLAY:
|
||||
// Only resume from PAUSED; don't fabricate a PLAYING state from IDLE/ERROR.
|
||||
if (this->get_state() != media_source::MediaSourceState::PAUSED)
|
||||
break;
|
||||
this->set_state_(media_source::MediaSourceState::PLAYING);
|
||||
this->pause_.store(false, std::memory_order_relaxed);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Called from the decoder task. Forwards to the orchestrator's listener, which is responsible for
|
||||
// being thread-safe with respect to its own audio writer.
|
||||
size_t AudioHTTPMediaSource::on_audio_write(const uint8_t *data, size_t length, uint32_t timeout_ms) {
|
||||
if (this->pause_.load(std::memory_order_relaxed)) {
|
||||
vTaskDelay(pdMS_TO_TICKS(PAUSE_POLL_DELAY_MS));
|
||||
return 0;
|
||||
}
|
||||
return this->write_output(data, length, timeout_ms, this->stream_info_);
|
||||
}
|
||||
|
||||
// Called from the decoder task before the first on_audio_write().
|
||||
void AudioHTTPMediaSource::on_stream_info(const micro_decoder::AudioStreamInfo &info) {
|
||||
this->stream_info_ = audio::AudioStreamInfo(info.get_bits_per_sample(), info.get_channels(), info.get_sample_rate());
|
||||
}
|
||||
|
||||
// microDecoder invokes on_state_change() from inside decoder_->loop(), so this runs on the main
|
||||
// loop thread and it's safe to call set_state_() directly.
|
||||
void AudioHTTPMediaSource::on_state_change(micro_decoder::DecoderState state) {
|
||||
switch (state) {
|
||||
case micro_decoder::DecoderState::IDLE:
|
||||
this->set_state_(media_source::MediaSourceState::IDLE);
|
||||
this->disable_loop();
|
||||
break;
|
||||
case micro_decoder::DecoderState::PLAYING:
|
||||
this->set_state_(media_source::MediaSourceState::PLAYING);
|
||||
break;
|
||||
case micro_decoder::DecoderState::FAILED:
|
||||
this->set_state_(media_source::MediaSourceState::ERROR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::audio_http
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/audio/audio.h"
|
||||
#include "esphome/components/media_source/media_source.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <micro_decoder/decoder_source.h>
|
||||
#include <micro_decoder/types.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace esphome::audio_http {
|
||||
|
||||
// Inherits from two unrelated listener-style interfaces:
|
||||
// - media_source::MediaSource: this source reports state and writes audio *to* an orchestrator
|
||||
// (the orchestrator calls set_listener() on us with a MediaSourceListener*).
|
||||
// - micro_decoder::DecoderListener: the underlying decoder calls back *into* us with decoded
|
||||
// audio and state changes (we call decoder_->set_listener(this) in setup()).
|
||||
// The two set_listener() methods live on different base classes and serve opposite directions.
|
||||
class AudioHTTPMediaSource : public Component, public media_source::MediaSource, public micro_decoder::DecoderListener {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_buffer_size(size_t buffer_size) { this->buffer_size_ = buffer_size; }
|
||||
void set_task_stack_in_psram(bool task_stack_in_psram) { this->decoder_task_stack_in_psram_ = task_stack_in_psram; }
|
||||
|
||||
// MediaSource interface implementation
|
||||
bool play_uri(const std::string &uri) override;
|
||||
void handle_command(media_source::MediaSourceCommand command) override;
|
||||
bool can_handle(const std::string &uri) const override;
|
||||
|
||||
// DecoderListener interface implementation
|
||||
size_t on_audio_write(const uint8_t *data, size_t length, uint32_t timeout_ms) override;
|
||||
void on_stream_info(const micro_decoder::AudioStreamInfo &info) override;
|
||||
void on_state_change(micro_decoder::DecoderState state) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<micro_decoder::DecoderSource> decoder_;
|
||||
audio::AudioStreamInfo stream_info_;
|
||||
|
||||
size_t buffer_size_{50000};
|
||||
|
||||
// Written from the main loop in handle_command(), read from the decoder task in
|
||||
// on_audio_write(). Must be atomic to avoid a data race.
|
||||
std::atomic<bool> pause_{false};
|
||||
bool decoder_task_stack_in_psram_{false};
|
||||
};
|
||||
|
||||
} // namespace esphome::audio_http
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -1,59 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import audio, esp32, media_source, psram
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TASK_STACK_IN_PSRAM
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
AUTO_LOAD = ["audio"]
|
||||
|
||||
audio_http_ns = cg.esphome_ns.namespace("audio_http")
|
||||
AudioHTTPMediaSource = audio_http_ns.class_(
|
||||
"AudioHTTPMediaSource", cg.Component, media_source.MediaSource
|
||||
)
|
||||
|
||||
|
||||
def _request_micro_decoder(config: ConfigType) -> ConfigType:
|
||||
audio.request_micro_decoder_support()
|
||||
return config
|
||||
|
||||
|
||||
def _validate_task_stack_in_psram(value: Any) -> bool:
|
||||
# Only require the psram component when actually enabling PSRAM stacks; validating
|
||||
# the boolean first means `false` doesn't trigger the requires_component check.
|
||||
if value := cv.boolean(value):
|
||||
return cv.requires_component(psram.DOMAIN)(value)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
media_source.media_source_schema(
|
||||
AudioHTTPMediaSource,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=50000): cv.int_range(
|
||||
min=5000, max=1000000
|
||||
),
|
||||
cv.Optional(CONF_TASK_STACK_IN_PSRAM): _validate_task_stack_in_psram,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on_esp32,
|
||||
_request_micro_decoder,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await media_source.register_media_source(var, config)
|
||||
|
||||
if config.get(CONF_TASK_STACK_IN_PSRAM):
|
||||
cg.add(var.set_task_stack_in_psram(True))
|
||||
esp32.add_idf_sdkconfig_option(
|
||||
"CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY", True
|
||||
)
|
||||
cg.add(var.set_buffer_size(config[CONF_BUFFER_SIZE]))
|
||||
@@ -154,7 +154,7 @@ void BH1750Sensor::loop() {
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(lx);
|
||||
this->state_ = IDLE;
|
||||
|
||||
@@ -62,7 +62,6 @@ from esphome.const import (
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
@@ -333,9 +332,8 @@ def parse_multi_click_timing_str(value):
|
||||
try:
|
||||
state = cv.boolean(parts[0])
|
||||
except cv.Invalid:
|
||||
raise cv.Invalid(
|
||||
f"First word must either be ON or OFF, not {parts[0]}"
|
||||
) from None
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(f"First word must either be ON or OFF, not {parts[0]}")
|
||||
|
||||
if parts[1] != "for":
|
||||
raise cv.Invalid(f"Second word must be 'for', got {parts[1]}")
|
||||
@@ -352,9 +350,7 @@ def parse_multi_click_timing_str(value):
|
||||
try:
|
||||
length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(
|
||||
f"Multi Click Grammar Parsing length failed: {err}"
|
||||
) from err
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
|
||||
return {CONF_STATE: state, key: str(length)}
|
||||
|
||||
if parts[3] != "to":
|
||||
@@ -363,16 +359,12 @@ def parse_multi_click_timing_str(value):
|
||||
try:
|
||||
min_length = cv.positive_time_period_milliseconds(parts[2])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(
|
||||
f"Multi Click Grammar Parsing minimum length failed: {err}"
|
||||
) from err
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
|
||||
|
||||
try:
|
||||
max_length = cv.positive_time_period_milliseconds(parts[4])
|
||||
except cv.Invalid as err:
|
||||
raise cv.Invalid(
|
||||
f"Multi Click Grammar Parsing maximum length failed: {err}"
|
||||
) from err
|
||||
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
|
||||
|
||||
return {
|
||||
CONF_STATE: state,
|
||||
@@ -625,7 +617,7 @@ async def setup_binary_sensor_core_(var, config):
|
||||
async def register_binary_sensor(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
queue_entity_register("binary_sensor", config)
|
||||
cg.add(cg.App.register_binary_sensor(var))
|
||||
CORE.register_platform_component("binary_sensor", var)
|
||||
await setup_binary_sensor_core_(var, config)
|
||||
|
||||
|
||||
@@ -50,31 +50,29 @@ void MultiClickTriggerBase::on_state_(bool state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// at_index_ has a value here (the !has_value() branch above returns).
|
||||
size_t at_index = *this->at_index_;
|
||||
if (at_index == this->timing_count_) {
|
||||
if (*this->at_index_ == this->timing_count_) {
|
||||
this->trigger_();
|
||||
return;
|
||||
}
|
||||
|
||||
MultiClickTriggerEvent evt = this->timing_[at_index];
|
||||
MultiClickTriggerEvent evt = this->timing_[*this->at_index_];
|
||||
|
||||
if (evt.max_length != 4294967294UL) {
|
||||
ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, at_index, evt.min_length, evt.max_length); // NOLINT
|
||||
ESP_LOGV(TAG, "A i=%zu min=%" PRIu32 " max=%" PRIu32, *this->at_index_, evt.min_length, evt.max_length); // NOLINT
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
} else if (at_index + 1 != this->timing_count_) {
|
||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, at_index, evt.min_length); // NOLINT
|
||||
} else if (*this->at_index_ + 1 != this->timing_count_) {
|
||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, at_index, evt.min_length); // NOLINT
|
||||
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->is_valid_ = false;
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||
}
|
||||
|
||||
this->at_index_ = at_index + 1;
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
}
|
||||
void MultiClickTriggerBase::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
|
||||
@@ -65,8 +65,3 @@ async def to_code(config):
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("bk72xx", PIN_SCHEMA)
|
||||
async def pin_to_code(config):
|
||||
return await libretiny.gpio.component_pin_to_code(config)
|
||||
|
||||
|
||||
# Called by writer.py; delegates to the shared libretiny implementation.
|
||||
def copy_files() -> None:
|
||||
libretiny.copy_files()
|
||||
|
||||
@@ -16,7 +16,6 @@ from esphome.components.libretiny.const import (
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_BK7231Q,
|
||||
FAMILY_BK7231T,
|
||||
FAMILY_BK7238,
|
||||
FAMILY_BK7251,
|
||||
)
|
||||
|
||||
@@ -25,32 +24,16 @@ BK72XX_BOARDS = {
|
||||
"name": "WB2L_M1 Wi-Fi Module",
|
||||
"family": FAMILY_BK7231N,
|
||||
},
|
||||
"xh-wb3s": {
|
||||
"name": "NiceMCU XH-WB3S",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"cbu": {
|
||||
"name": "CBU Wi-Fi Module",
|
||||
"family": FAMILY_BK7231N,
|
||||
},
|
||||
"t1-u": {
|
||||
"name": "T1-U Wi-Fi Module",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"generic-bk7238-tuya": {
|
||||
"name": "Generic - BK7238 (Tuya T1)",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"t1-m": {
|
||||
"name": "T1-M Wi-Fi Module",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"generic-bk7231t-qfn32-tuya": {
|
||||
"name": "Generic - BK7231T (Tuya)",
|
||||
"name": "Generic - BK7231T (Tuya QFN32)",
|
||||
"family": FAMILY_BK7231T,
|
||||
},
|
||||
"generic-bk7231n-qfn32-tuya": {
|
||||
"name": "Generic - BK7231N (Tuya)",
|
||||
"name": "Generic - BK7231N (Tuya QFN32)",
|
||||
"family": FAMILY_BK7231N,
|
||||
},
|
||||
"cb1s": {
|
||||
@@ -81,10 +64,6 @@ BK72XX_BOARDS = {
|
||||
"name": "Generic - BK7252",
|
||||
"family": FAMILY_BK7251,
|
||||
},
|
||||
"t1-3s": {
|
||||
"name": "T1-3S Wi-Fi Module",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"wb2l": {
|
||||
"name": "WB2L Wi-Fi Module",
|
||||
"family": FAMILY_BK7231T,
|
||||
@@ -101,10 +80,6 @@ BK72XX_BOARDS = {
|
||||
"name": "CB2S Wi-Fi Module",
|
||||
"family": FAMILY_BK7231N,
|
||||
},
|
||||
"generic-bk7238": {
|
||||
"name": "Generic - BK7238",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"wa2": {
|
||||
"name": "WA2 Wi-Fi Module",
|
||||
"family": FAMILY_BK7231Q,
|
||||
@@ -125,10 +100,6 @@ BK72XX_BOARDS = {
|
||||
"name": "WB3L Wi-Fi Module",
|
||||
"family": FAMILY_BK7231T,
|
||||
},
|
||||
"t1-2s": {
|
||||
"name": "T1-2S Wi-Fi Module",
|
||||
"family": FAMILY_BK7238,
|
||||
},
|
||||
"wb2s": {
|
||||
"name": "WB2S Wi-Fi Module",
|
||||
"family": FAMILY_BK7231T,
|
||||
@@ -187,83 +158,6 @@ BK72XX_BOARD_PINS = {
|
||||
"D12": 22,
|
||||
"A0": 23,
|
||||
},
|
||||
"xh-wb3s": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
"SPI0_MOSI": 16,
|
||||
"SPI0_SCK": 14,
|
||||
"WIRE2_SCL_0": 15,
|
||||
"WIRE2_SCL_1": 24,
|
||||
"WIRE2_SDA_0": 17,
|
||||
"WIRE2_SDA_1": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC3": 20,
|
||||
"ADC4": 28,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"CS": 15,
|
||||
"MISO": 17,
|
||||
"MOSI": 16,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P7": 7,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P14": 14,
|
||||
"P15": 15,
|
||||
"P16": 16,
|
||||
"P17": 17,
|
||||
"P20": 20,
|
||||
"P21": 21,
|
||||
"P22": 22,
|
||||
"P23": 23,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"P28": 28,
|
||||
"PWM0": 6,
|
||||
"PWM1": 7,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCK": 14,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 7,
|
||||
"D1": 23,
|
||||
"D2": 14,
|
||||
"D3": 26,
|
||||
"D4": 24,
|
||||
"D5": 6,
|
||||
"D6": 9,
|
||||
"D7": 0,
|
||||
"D8": 1,
|
||||
"D9": 8,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 16,
|
||||
"D13": 20,
|
||||
"D14": 21,
|
||||
"D15": 22,
|
||||
"D16": 15,
|
||||
"D17": 17,
|
||||
"A0": 28,
|
||||
"A1": 26,
|
||||
"A2": 24,
|
||||
"A3": 1,
|
||||
"A4": 10,
|
||||
"A5": 20,
|
||||
},
|
||||
"cbu": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
@@ -336,204 +230,6 @@ BK72XX_BOARD_PINS = {
|
||||
"D18": 21,
|
||||
"A0": 23,
|
||||
},
|
||||
"t1-u": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
"SPI0_MOSI": 16,
|
||||
"SPI0_SCK": 14,
|
||||
"WIRE2_SCL_0": 15,
|
||||
"WIRE2_SCL_1": 24,
|
||||
"WIRE2_SDA_0": 17,
|
||||
"WIRE2_SDA_1": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC3": 20,
|
||||
"ADC4": 28,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"CS": 15,
|
||||
"MISO": 17,
|
||||
"MOSI": 16,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P14": 14,
|
||||
"P15": 15,
|
||||
"P16": 16,
|
||||
"P17": 17,
|
||||
"P20": 20,
|
||||
"P21": 21,
|
||||
"P22": 22,
|
||||
"P23": 23,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"P28": 28,
|
||||
"PWM0": 6,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCK": 14,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 14,
|
||||
"D1": 16,
|
||||
"D2": 23,
|
||||
"D3": 22,
|
||||
"D4": 20,
|
||||
"D5": 1,
|
||||
"D6": 0,
|
||||
"D7": 24,
|
||||
"D8": 9,
|
||||
"D9": 26,
|
||||
"D10": 6,
|
||||
"D11": 8,
|
||||
"D12": 11,
|
||||
"D13": 10,
|
||||
"D14": 28,
|
||||
"D15": 21,
|
||||
"D16": 17,
|
||||
"D17": 15,
|
||||
"A0": 20,
|
||||
"A1": 1,
|
||||
"A2": 24,
|
||||
"A3": 26,
|
||||
"A4": 10,
|
||||
"A5": 28,
|
||||
},
|
||||
"generic-bk7238-tuya": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
"SPI0_MOSI": 16,
|
||||
"SPI0_SCK": 14,
|
||||
"WIRE2_SCL_0": 15,
|
||||
"WIRE2_SCL_1": 24,
|
||||
"WIRE2_SDA_0": 17,
|
||||
"WIRE2_SDA_1": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC3": 20,
|
||||
"ADC4": 28,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"CS": 15,
|
||||
"MISO": 17,
|
||||
"MOSI": 16,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P7": 7,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P14": 14,
|
||||
"P15": 15,
|
||||
"P16": 16,
|
||||
"P17": 17,
|
||||
"P20": 20,
|
||||
"P21": 21,
|
||||
"P22": 22,
|
||||
"P23": 23,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"P28": 28,
|
||||
"PWM0": 6,
|
||||
"PWM1": 7,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCK": 14,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 6,
|
||||
"D3": 7,
|
||||
"D4": 8,
|
||||
"D5": 9,
|
||||
"D6": 10,
|
||||
"D7": 11,
|
||||
"D8": 14,
|
||||
"D9": 15,
|
||||
"D10": 16,
|
||||
"D11": 17,
|
||||
"D12": 20,
|
||||
"D13": 21,
|
||||
"D14": 22,
|
||||
"D15": 23,
|
||||
"D16": 24,
|
||||
"D17": 26,
|
||||
"D18": 28,
|
||||
"A0": 1,
|
||||
"A1": 10,
|
||||
"A2": 20,
|
||||
"A3": 24,
|
||||
"A4": 26,
|
||||
"A5": 28,
|
||||
},
|
||||
"t1-m": {
|
||||
"WIRE2_SCL": 24,
|
||||
"WIRE2_SDA": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"PWM0": 6,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCL2": 24,
|
||||
"SDA2": 26,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 26,
|
||||
"D1": 6,
|
||||
"D2": 8,
|
||||
"D3": 1,
|
||||
"D4": 10,
|
||||
"D5": 11,
|
||||
"D6": 9,
|
||||
"D7": 24,
|
||||
"D11": 0,
|
||||
"A0": 26,
|
||||
"A1": 10,
|
||||
"A2": 1,
|
||||
"A3": 24,
|
||||
},
|
||||
"generic-bk7231t-qfn32-tuya": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
@@ -1085,75 +781,6 @@ BK72XX_BOARD_PINS = {
|
||||
"A6": 12,
|
||||
"A7": 13,
|
||||
},
|
||||
"t1-3s": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
"SPI0_MOSI": 16,
|
||||
"SPI0_SCK": 14,
|
||||
"WIRE2_SCL_0": 15,
|
||||
"WIRE2_SCL_1": 24,
|
||||
"WIRE2_SDA_0": 17,
|
||||
"WIRE2_SDA_1": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC3": 20,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"CS": 15,
|
||||
"MISO": 17,
|
||||
"MOSI": 16,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P14": 14,
|
||||
"P15": 15,
|
||||
"P16": 16,
|
||||
"P17": 17,
|
||||
"P20": 20,
|
||||
"P22": 22,
|
||||
"P23": 23,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"PWM0": 6,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCK": 14,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 20,
|
||||
"D1": 22,
|
||||
"D2": 6,
|
||||
"D3": 8,
|
||||
"D4": 9,
|
||||
"D5": 23,
|
||||
"D6": 0,
|
||||
"D7": 1,
|
||||
"D8": 24,
|
||||
"D9": 26,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 17,
|
||||
"D13": 16,
|
||||
"D14": 15,
|
||||
"D15": 14,
|
||||
"A0": 20,
|
||||
"A1": 1,
|
||||
"A2": 24,
|
||||
"A3": 26,
|
||||
"A4": 10,
|
||||
},
|
||||
"wb2l": {
|
||||
"WIRE1_SCL": 20,
|
||||
"WIRE1_SDA": 21,
|
||||
@@ -1338,84 +965,6 @@ BK72XX_BOARD_PINS = {
|
||||
"D10": 21,
|
||||
"A0": 23,
|
||||
},
|
||||
"generic-bk7238": {
|
||||
"SPI0_CS": 15,
|
||||
"SPI0_MISO": 17,
|
||||
"SPI0_MOSI": 16,
|
||||
"SPI0_SCK": 14,
|
||||
"WIRE2_SCL_0": 15,
|
||||
"WIRE2_SCL_1": 24,
|
||||
"WIRE2_SDA_0": 17,
|
||||
"WIRE2_SDA_1": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC3": 20,
|
||||
"ADC4": 28,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"CS": 15,
|
||||
"MISO": 17,
|
||||
"MOSI": 16,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P7": 7,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P14": 14,
|
||||
"P15": 15,
|
||||
"P16": 16,
|
||||
"P17": 17,
|
||||
"P20": 20,
|
||||
"P21": 21,
|
||||
"P22": 22,
|
||||
"P23": 23,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"P28": 28,
|
||||
"PWM0": 6,
|
||||
"PWM1": 7,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCK": 14,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 6,
|
||||
"D3": 7,
|
||||
"D4": 8,
|
||||
"D5": 9,
|
||||
"D6": 10,
|
||||
"D7": 11,
|
||||
"D8": 14,
|
||||
"D9": 15,
|
||||
"D10": 16,
|
||||
"D11": 17,
|
||||
"D12": 20,
|
||||
"D13": 21,
|
||||
"D14": 22,
|
||||
"D15": 23,
|
||||
"D16": 24,
|
||||
"D17": 26,
|
||||
"D18": 28,
|
||||
"A0": 1,
|
||||
"A1": 10,
|
||||
"A2": 20,
|
||||
"A3": 24,
|
||||
"A4": 26,
|
||||
"A5": 28,
|
||||
},
|
||||
"wa2": {
|
||||
"WIRE1_SCL": 20,
|
||||
"WIRE1_SDA": 21,
|
||||
@@ -1686,51 +1235,6 @@ BK72XX_BOARD_PINS = {
|
||||
"D15": 1,
|
||||
"A0": 23,
|
||||
},
|
||||
"t1-2s": {
|
||||
"WIRE2_SCL": 24,
|
||||
"WIRE2_SDA": 26,
|
||||
"SERIAL1_RX": 10,
|
||||
"SERIAL1_TX": 11,
|
||||
"SERIAL2_RX": 1,
|
||||
"SERIAL2_TX": 0,
|
||||
"ADC1": 26,
|
||||
"ADC2": 24,
|
||||
"ADC5": 1,
|
||||
"ADC6": 10,
|
||||
"P0": 0,
|
||||
"P1": 1,
|
||||
"P6": 6,
|
||||
"P8": 8,
|
||||
"P9": 9,
|
||||
"P10": 10,
|
||||
"P11": 11,
|
||||
"P24": 24,
|
||||
"P26": 26,
|
||||
"PWM0": 6,
|
||||
"PWM2": 8,
|
||||
"PWM3": 9,
|
||||
"PWM4": 24,
|
||||
"PWM5": 26,
|
||||
"RX1": 10,
|
||||
"RX2": 1,
|
||||
"SCL2": 24,
|
||||
"SDA2": 26,
|
||||
"TX1": 11,
|
||||
"TX2": 0,
|
||||
"D0": 26,
|
||||
"D1": 6,
|
||||
"D2": 8,
|
||||
"D3": 1,
|
||||
"D4": 10,
|
||||
"D5": 11,
|
||||
"D6": 9,
|
||||
"D7": 24,
|
||||
"D11": 0,
|
||||
"A0": 26,
|
||||
"A1": 10,
|
||||
"A2": 1,
|
||||
"A3": 24,
|
||||
},
|
||||
"wb2s": {
|
||||
"WIRE1_SCL": 20,
|
||||
"WIRE1_SDA": 21,
|
||||
|
||||
@@ -20,77 +20,58 @@ constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data)
|
||||
}
|
||||
|
||||
void BL0906::loop() {
|
||||
while (this->available())
|
||||
this->flush();
|
||||
|
||||
if (this->current_stage_ == STAGE_IDLE) {
|
||||
// Woken up between cycles to drain the action queue. Go back to sleep.
|
||||
this->handle_actions_();
|
||||
this->disable_loop();
|
||||
if (this->current_channel_ == UINT8_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->current_stage_ == STAGE_TEMP) {
|
||||
while (this->available())
|
||||
this->flush();
|
||||
|
||||
if (this->current_channel_ == 0) {
|
||||
// Temperature
|
||||
this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_1) {
|
||||
} else if (this->current_channel_ == 1) {
|
||||
this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_);
|
||||
this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_);
|
||||
this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_2) {
|
||||
} else if (this->current_channel_ == 2) {
|
||||
this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_);
|
||||
this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_);
|
||||
this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_3) {
|
||||
} else if (this->current_channel_ == 3) {
|
||||
this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_);
|
||||
this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_);
|
||||
this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_4) {
|
||||
} else if (this->current_channel_ == 4) {
|
||||
this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_);
|
||||
this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_);
|
||||
this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_5) {
|
||||
} else if (this->current_channel_ == 5) {
|
||||
this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_);
|
||||
this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_);
|
||||
this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_CHANNEL_6) {
|
||||
} else if (this->current_channel_ == 6) {
|
||||
this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_);
|
||||
this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_);
|
||||
this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_FREQ) {
|
||||
} else if (this->current_channel_ == UINT8_MAX - 2) {
|
||||
// Frequency
|
||||
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, this->frequency_sensor_);
|
||||
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_);
|
||||
// Voltage
|
||||
this->read_data_(BL0906_V_RMS, BL0906_UREF, this->voltage_sensor_);
|
||||
} else if (this->current_stage_ == STAGE_POWER) {
|
||||
this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_);
|
||||
} else if (this->current_channel_ == UINT8_MAX - 1) {
|
||||
// Total power
|
||||
this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_);
|
||||
// Total Energy
|
||||
this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_);
|
||||
} else {
|
||||
this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage
|
||||
return;
|
||||
}
|
||||
this->advance_stage_();
|
||||
this->current_channel_++;
|
||||
this->handle_actions_();
|
||||
}
|
||||
|
||||
void BL0906::advance_stage_() {
|
||||
switch (this->current_stage_) {
|
||||
case STAGE_CHANNEL_6:
|
||||
this->current_stage_ = STAGE_FREQ;
|
||||
break;
|
||||
case STAGE_FREQ:
|
||||
this->current_stage_ = STAGE_POWER;
|
||||
break;
|
||||
case STAGE_POWER:
|
||||
// Cycle complete; sleep until the next update().
|
||||
this->current_stage_ = STAGE_IDLE;
|
||||
this->disable_loop();
|
||||
break;
|
||||
default:
|
||||
this->current_stage_ = static_cast<BL0906Stage>(this->current_stage_ + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BL0906::setup() {
|
||||
while (this->available())
|
||||
this->flush();
|
||||
@@ -104,20 +85,12 @@ void BL0906::setup() {
|
||||
this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6
|
||||
|
||||
this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD));
|
||||
|
||||
// Loop stays idle until the first update() or enqueued action.
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void BL0906::update() {
|
||||
this->current_stage_ = STAGE_TEMP;
|
||||
this->enable_loop();
|
||||
}
|
||||
void BL0906::update() { this->current_channel_ = 0; }
|
||||
|
||||
size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) {
|
||||
this->action_queue_.push_back(function);
|
||||
// Ensure the queue is serviced even if the read cycle has already completed.
|
||||
this->enable_loop();
|
||||
return this->action_queue_.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,22 +12,6 @@
|
||||
namespace esphome {
|
||||
namespace bl0906 {
|
||||
|
||||
// Stage values for the read state machine. After STAGE_CHANNEL_6 the state machine
|
||||
// jumps to the two sentinel stages below, then to STAGE_IDLE which marks the cycle
|
||||
// as complete and disables the loop.
|
||||
enum BL0906Stage : uint8_t {
|
||||
STAGE_TEMP = 0, // chip temperature
|
||||
STAGE_CHANNEL_1 = 1, // per-phase current + power + energy
|
||||
STAGE_CHANNEL_2 = 2,
|
||||
STAGE_CHANNEL_3 = 3,
|
||||
STAGE_CHANNEL_4 = 4,
|
||||
STAGE_CHANNEL_5 = 5,
|
||||
STAGE_CHANNEL_6 = 6,
|
||||
STAGE_FREQ = UINT8_MAX - 2, // frequency + voltage
|
||||
STAGE_POWER = UINT8_MAX - 1, // total power + total energy
|
||||
STAGE_IDLE = UINT8_MAX, // cycle complete
|
||||
};
|
||||
|
||||
struct DataPacket { // NOLINT(altera-struct-pack-align)
|
||||
uint8_t l{0};
|
||||
uint8_t m{0};
|
||||
@@ -95,8 +79,7 @@ class BL0906 : public PollingComponent, public uart::UARTDevice {
|
||||
|
||||
void bias_correction_(uint8_t address, float measurements, float correction);
|
||||
|
||||
BL0906Stage current_stage_{STAGE_IDLE};
|
||||
void advance_stage_();
|
||||
uint8_t current_channel_{0};
|
||||
size_t enqueue_action_(ActionCallbackFuncPtr function);
|
||||
void handle_actions_();
|
||||
|
||||
|
||||
@@ -30,6 +30,19 @@ void BluetoothProxy::setup() {
|
||||
this->configured_scan_active_ = this->parent_->get_scan_active();
|
||||
|
||||
this->parent_->add_scanner_state_listener(this);
|
||||
|
||||
this->set_interval(100, [this]() {
|
||||
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
|
||||
this->flush_pending_advertisements_();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void BluetoothProxy::on_scanner_state(esp32_ble_tracker::ScannerState state) {
|
||||
@@ -120,25 +133,6 @@ void BluetoothProxy::dump_config() {
|
||||
YESNO(this->active_), this->connection_count_);
|
||||
}
|
||||
|
||||
void BluetoothProxy::loop() {
|
||||
// Run advertisement flush / connection cleanup every 100ms
|
||||
uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_advertisement_flush_time_ < 100)
|
||||
return;
|
||||
this->last_advertisement_flush_time_ = now;
|
||||
|
||||
if (api::global_api_server->is_connected() && this->api_connection_ != nullptr) {
|
||||
this->flush_pending_advertisements_();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->connection_count_; i++) {
|
||||
auto *connection = this->connections_[i];
|
||||
if (connection->get_address() != 0 && !connection->disconnect_pending()) {
|
||||
connection->disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
|
||||
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
|
||||
}
|
||||
@@ -207,6 +201,7 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
|
||||
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
|
||||
this->log_connection_info_(connection, "v3 without cache");
|
||||
}
|
||||
uint64_to_bd_addr(msg.address, connection->remote_bda_);
|
||||
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
|
||||
connection->set_state(espbt::ClientState::DISCOVERED);
|
||||
this->send_connections_free();
|
||||
|
||||
@@ -65,7 +65,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
void register_connection(BluetoothConnection *connection) {
|
||||
@@ -177,9 +176,6 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener,
|
||||
// BLE advertisement batching
|
||||
api::BluetoothLERawAdvertisementsResponse response_;
|
||||
|
||||
// Group 3: 4-byte types
|
||||
uint32_t last_advertisement_flush_time_{0};
|
||||
|
||||
// Pre-allocated response message - always ready to send
|
||||
api::BluetoothConnectionsFreeResponse connections_free_response_;
|
||||
|
||||
|
||||
@@ -78,43 +78,43 @@ void BME680Component::setup() {
|
||||
}
|
||||
|
||||
// Read calibration
|
||||
uint8_t coeff1[25];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF1, coeff1, 25)) {
|
||||
uint8_t cal1[25];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF1, cal1, 25)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint8_t coeff2[16];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF2, coeff2, 16)) {
|
||||
uint8_t cal2[16];
|
||||
if (!this->read_bytes(BME680_REGISTER_COEFF2, cal2, 16)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->calibration_.t1 = coeff2[9] << 8 | coeff2[8];
|
||||
this->calibration_.t2 = coeff1[2] << 8 | coeff1[1];
|
||||
this->calibration_.t3 = coeff1[3];
|
||||
this->calibration_.t1 = cal2[9] << 8 | cal2[8];
|
||||
this->calibration_.t2 = cal1[2] << 8 | cal1[1];
|
||||
this->calibration_.t3 = cal1[3];
|
||||
|
||||
this->calibration_.h1 = coeff2[2] << 4 | (coeff2[1] & 0x0F);
|
||||
this->calibration_.h2 = coeff2[0] << 4 | coeff2[1] >> 4;
|
||||
this->calibration_.h3 = coeff2[3];
|
||||
this->calibration_.h4 = coeff2[4];
|
||||
this->calibration_.h5 = coeff2[5];
|
||||
this->calibration_.h6 = coeff2[6];
|
||||
this->calibration_.h7 = coeff2[7];
|
||||
this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F);
|
||||
this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4;
|
||||
this->calibration_.h3 = cal2[3];
|
||||
this->calibration_.h4 = cal2[4];
|
||||
this->calibration_.h5 = cal2[5];
|
||||
this->calibration_.h6 = cal2[6];
|
||||
this->calibration_.h7 = cal2[7];
|
||||
|
||||
this->calibration_.p1 = coeff1[6] << 8 | coeff1[5];
|
||||
this->calibration_.p2 = coeff1[8] << 8 | coeff1[7];
|
||||
this->calibration_.p3 = coeff1[9];
|
||||
this->calibration_.p4 = coeff1[12] << 8 | coeff1[11];
|
||||
this->calibration_.p5 = coeff1[14] << 8 | coeff1[13];
|
||||
this->calibration_.p6 = coeff1[16];
|
||||
this->calibration_.p7 = coeff1[15];
|
||||
this->calibration_.p8 = coeff1[20] << 8 | coeff1[19];
|
||||
this->calibration_.p9 = coeff1[22] << 8 | coeff1[21];
|
||||
this->calibration_.p10 = coeff1[23];
|
||||
this->calibration_.p1 = cal1[6] << 8 | cal1[5];
|
||||
this->calibration_.p2 = cal1[8] << 8 | cal1[7];
|
||||
this->calibration_.p3 = cal1[9];
|
||||
this->calibration_.p4 = cal1[12] << 8 | cal1[11];
|
||||
this->calibration_.p5 = cal1[14] << 8 | cal1[13];
|
||||
this->calibration_.p6 = cal1[16];
|
||||
this->calibration_.p7 = cal1[15];
|
||||
this->calibration_.p8 = cal1[20] << 8 | cal1[19];
|
||||
this->calibration_.p9 = cal1[22] << 8 | cal1[21];
|
||||
this->calibration_.p10 = cal1[23];
|
||||
|
||||
this->calibration_.gh1 = coeff2[14];
|
||||
this->calibration_.gh2 = coeff2[12] << 8 | coeff2[13];
|
||||
this->calibration_.gh3 = coeff2[15];
|
||||
this->calibration_.gh1 = cal2[14];
|
||||
this->calibration_.gh2 = cal2[12] << 8 | cal2[13];
|
||||
this->calibration_.gh3 = cal2[15];
|
||||
|
||||
uint8_t temp_var = 0;
|
||||
if (!this->read_byte(0x02, &temp_var)) {
|
||||
|
||||
@@ -6,7 +6,6 @@ from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET, Fr
|
||||
CODEOWNERS = ["@trvrnrth"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensor", "text_sensor"]
|
||||
CONFLICTS_WITH = ["bme68x_bsec2"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_BME680_BSEC_ID = "bme680_bsec_id"
|
||||
|
||||
@@ -13,12 +13,10 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@neffs", "@kbx81"]
|
||||
CONFLICTS_WITH = ["bme680_bsec"]
|
||||
|
||||
DOMAIN = "bme68x_bsec2"
|
||||
|
||||
BSEC2_LIBRARY_VERSION = "1.10.2610"
|
||||
BME68x_LIBRARY_VERSION = "v1.3.40408"
|
||||
|
||||
CONF_ALGORITHM_OUTPUT = "algorithm_output"
|
||||
CONF_BME68X_BSEC2_ID = "bme68x_bsec2_id"
|
||||
@@ -172,9 +170,7 @@ async def to_code_base(config):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
bsec2_iaq_config = f.read()
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(
|
||||
f"Could not open binary configuration file {path}: {e}"
|
||||
) from e
|
||||
raise core.EsphomeError(f"Could not open binary configuration file {path}: {e}")
|
||||
|
||||
# Convert retrieved BSEC2 config to an array of ints
|
||||
rhs = [int(x) for x in bsec2_iaq_config.split(",")]
|
||||
@@ -188,31 +184,16 @@ async def to_code_base(config):
|
||||
if core.CORE.using_arduino:
|
||||
cg.add_library("Wire", None)
|
||||
cg.add_library("SPI", None)
|
||||
|
||||
if core.CORE.is_esp32:
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
|
||||
add_idf_component(
|
||||
name="boschsensortec/Bosch-BME68x-Library",
|
||||
repo="https://github.com/esphome-libs/Bosch-BME68x-Library",
|
||||
ref=BME68x_LIBRARY_VERSION,
|
||||
)
|
||||
add_idf_component(
|
||||
name="boschsensortec/Bosch-BSEC2-Library",
|
||||
repo="https://github.com/esphome-libs/Bosch-BSEC2-Library",
|
||||
ref=BSEC2_LIBRARY_VERSION,
|
||||
)
|
||||
else:
|
||||
cg.add_library(
|
||||
"BME68x Sensor library",
|
||||
None,
|
||||
f"https://github.com/boschsensortec/Bosch-BME68x-Library#{BME68x_LIBRARY_VERSION}",
|
||||
)
|
||||
cg.add_library(
|
||||
"BSEC2 Software Library",
|
||||
None,
|
||||
f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}",
|
||||
)
|
||||
cg.add_library(
|
||||
"BME68x Sensor library",
|
||||
None,
|
||||
"https://github.com/boschsensortec/Bosch-BME68x-Library#v1.3.40408",
|
||||
)
|
||||
cg.add_library(
|
||||
"BSEC2 Software Library",
|
||||
None,
|
||||
f"https://github.com/boschsensortec/Bosch-BSEC2-Library.git#{BSEC2_LIBRARY_VERSION}",
|
||||
)
|
||||
|
||||
cg.add_define("USE_BSEC2")
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ from esphome.const import (
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
@@ -102,7 +101,7 @@ async def setup_button_core_(var, config):
|
||||
async def register_button(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
queue_entity_register("button", config)
|
||||
cg.add(cg.App.register_button(var))
|
||||
CORE.register_platform_component("button", var)
|
||||
await setup_button_core_(var, config)
|
||||
|
||||
|
||||
@@ -106,30 +106,6 @@ void IRAM_ATTR CC1101Component::gpio_intr(CC1101Component *arg) { arg->enable_lo
|
||||
|
||||
void CC1101Component::setup() {
|
||||
this->spi_setup();
|
||||
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->setup();
|
||||
}
|
||||
|
||||
this->configure();
|
||||
if (this->is_failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer pin mode setup until after all components have completed setup()
|
||||
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->defer([this]() {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
if (this->state_.PKT_FORMAT == static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
|
||||
this->gdo0_pin_->attach_interrupt(&CC1101Component::gpio_intr, this, gpio::INTERRUPT_RISING_EDGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::configure() {
|
||||
// Manual reset sequence per CC1101 datasheet section 19.1.2
|
||||
this->cs_->digital_write(true);
|
||||
delayMicroseconds(1);
|
||||
this->cs_->digital_write(false);
|
||||
@@ -152,6 +128,11 @@ void CC1101Component::configure() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup GDO0 pin if configured
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->gdo0_pin_->setup();
|
||||
}
|
||||
|
||||
this->initialized_ = true;
|
||||
|
||||
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
|
||||
@@ -161,11 +142,21 @@ void CC1101Component::configure() {
|
||||
this->write_(static_cast<Register>(i));
|
||||
}
|
||||
this->set_output_power(this->output_power_requested_);
|
||||
|
||||
if (!this->enter_rx_()) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Defer pin mode setup until after all components have completed setup()
|
||||
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
|
||||
if (this->gdo0_pin_ != nullptr) {
|
||||
this->defer([this]() {
|
||||
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
if (this->state_.PKT_FORMAT == static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
|
||||
this->gdo0_pin_->attach_interrupt(&CC1101Component::gpio_intr, this, gpio::INTERRUPT_RISING_EDGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi) {
|
||||
@@ -282,7 +273,7 @@ void CC1101Component::begin_rx() {
|
||||
|
||||
void CC1101Component::reset() {
|
||||
this->strobe_(Command::RES);
|
||||
this->configure();
|
||||
this->setup();
|
||||
}
|
||||
|
||||
void CC1101Component::set_idle() {
|
||||
|
||||
@@ -25,7 +25,6 @@ class CC1101Component : public Component,
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
void configure();
|
||||
|
||||
// Actions
|
||||
void begin_tx();
|
||||
|
||||
@@ -49,11 +49,7 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -446,7 +442,7 @@ async def setup_climate_core_(var, config):
|
||||
async def register_climate(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
queue_entity_register("climate", config)
|
||||
cg.add(cg.App.register_climate(var))
|
||||
CORE.register_platform_component("climate", var)
|
||||
await setup_climate_core_(var, config)
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ from esphome.const import (
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_device_class,
|
||||
setup_entity,
|
||||
)
|
||||
@@ -233,7 +232,7 @@ async def setup_cover_core_(var, config):
|
||||
async def register_cover(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
queue_entity_register("cover", config)
|
||||
cg.add(cg.App.register_cover(var))
|
||||
CORE.register_platform_component("cover", var)
|
||||
await setup_cover_core_(var, config)
|
||||
|
||||
|
||||
@@ -204,27 +204,24 @@ void CSE7761Component::get_data_() {
|
||||
value = this->read_(CSE7761_REG_RMSIA, 3);
|
||||
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
||||
value = this->read_(CSE7761_REG_POWERPA, 4);
|
||||
// PowerPA is two's complement signed 32-bit per datasheet
|
||||
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : static_cast<int32_t>(value);
|
||||
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
|
||||
|
||||
value = this->read_(CSE7761_REG_RMSIB, 3);
|
||||
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
||||
value = this->read_(CSE7761_REG_POWERPB, 4);
|
||||
// PowerPB is two's complement signed 32-bit per datasheet
|
||||
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : static_cast<int32_t>(value);
|
||||
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
|
||||
|
||||
// convert values and publish to sensors
|
||||
|
||||
float voltage = static_cast<float>(this->data_.voltage_rms) / this->coefficient_by_unit_(RMS_UC);
|
||||
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
|
||||
for (uint8_t channel = 0; channel < 2; channel++) {
|
||||
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
|
||||
float active_power =
|
||||
static_cast<float>(this->data_.active_power[channel]) / this->coefficient_by_unit_(POWER_PAC); // W
|
||||
float amps = static_cast<float>(this->data_.current_rms[channel]) / this->coefficient_by_unit_(RMS_IAC); // A
|
||||
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
|
||||
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
|
||||
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
|
||||
if (channel == 0) {
|
||||
if (this->power_sensor_1_ != nullptr) {
|
||||
|
||||
@@ -11,8 +11,10 @@ struct CSE7761DataStruct {
|
||||
uint32_t frequency = 0;
|
||||
uint32_t voltage_rms = 0;
|
||||
uint32_t current_rms[2] = {0};
|
||||
int32_t active_power[2] = {0};
|
||||
uint32_t energy[2] = {0};
|
||||
uint32_t active_power[2] = {0};
|
||||
uint16_t coefficient[8] = {0};
|
||||
uint8_t energy_update = 0;
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,11 +22,7 @@ from esphome.const import (
|
||||
CONF_YEAR,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import (
|
||||
entity_duplicate_validator,
|
||||
queue_entity_register,
|
||||
setup_entity,
|
||||
)
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
|
||||
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
||||
@@ -164,7 +160,7 @@ async def register_datetime(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
entity_type = config[CONF_TYPE].lower()
|
||||
queue_entity_register(entity_type, config)
|
||||
cg.add(getattr(cg.App, f"register_{entity_type}")(var))
|
||||
CORE.register_platform_component(entity_type, var)
|
||||
await setup_datetime_core_(var, config)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ void DebugComponent::dump_config() {
|
||||
|
||||
char device_info_buffer[DEVICE_INFO_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
size_t pos = buf_append_str(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, ESPHOME_VERSION);
|
||||
size_t pos = buf_append_printf(device_info_buffer, DEVICE_INFO_BUFFER_SIZE, 0, "%s", ESPHOME_VERSION);
|
||||
|
||||
this->free_heap_ = get_free_heap_();
|
||||
ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_);
|
||||
|
||||
@@ -224,21 +224,17 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
const char *model = ESPHOME_VARIANT;
|
||||
|
||||
// Build features string
|
||||
pos = buf_append_str(buf, size, pos, "|Chip: ");
|
||||
pos = buf_append_str(buf, size, pos, model);
|
||||
pos = buf_append_str(buf, size, pos, " Features:");
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip: %s Features:", model);
|
||||
bool first_feature = true;
|
||||
for (const auto &feature : CHIP_FEATURES) {
|
||||
if (info.features & feature.bit) {
|
||||
pos = buf_append_str(buf, size, pos, first_feature ? "" : ", ");
|
||||
pos = buf_append_str(buf, size, pos, feature.name);
|
||||
pos = buf_append_printf(buf, size, pos, "%s%s", first_feature ? "" : ", ", feature.name);
|
||||
first_feature = false;
|
||||
info.features &= ~feature.bit;
|
||||
}
|
||||
}
|
||||
if (info.features != 0) {
|
||||
pos = buf_append_str(buf, size, pos, first_feature ? "" : ", ");
|
||||
pos = buf_append_printf(buf, size, pos, "Other:0x%" PRIx32, info.features);
|
||||
pos = buf_append_printf(buf, size, pos, "%sOther:0x%" PRIx32, first_feature ? "" : ", ", info.features);
|
||||
}
|
||||
pos = buf_append_printf(buf, size, pos, " Cores:%u Revision:%u", info.cores, info.revision);
|
||||
|
||||
@@ -271,20 +267,17 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
// Framework detection
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGD(TAG, " Framework: Arduino");
|
||||
pos = buf_append_str(buf, size, pos, "|Framework: Arduino");
|
||||
pos = buf_append_printf(buf, size, pos, "|Framework: Arduino");
|
||||
#else
|
||||
ESP_LOGD(TAG, " Framework: ESP-IDF");
|
||||
pos = buf_append_str(buf, size, pos, "|Framework: ESP-IDF");
|
||||
pos = buf_append_printf(buf, size, pos, "|Framework: ESP-IDF");
|
||||
#endif
|
||||
|
||||
pos = buf_append_str(buf, size, pos, "|ESP-IDF: ");
|
||||
pos = buf_append_str(buf, size, pos, esp_get_idf_version());
|
||||
pos = buf_append_printf(buf, size, pos, "|ESP-IDF: %s", esp_get_idf_version());
|
||||
pos = buf_append_printf(buf, size, pos, "|EFuse MAC: %02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
|
||||
mac[4], mac[5]);
|
||||
pos = buf_append_str(buf, size, pos, "|Reset: ");
|
||||
pos = buf_append_str(buf, size, pos, reset_reason);
|
||||
pos = buf_append_str(buf, size, pos, "|Wakeup: ");
|
||||
pos = buf_append_str(buf, size, pos, wakeup_cause);
|
||||
pos = buf_append_printf(buf, size, pos, "|Reset: %s", reset_reason);
|
||||
pos = buf_append_printf(buf, size, pos, "|Wakeup: %s", wakeup_cause);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -38,12 +38,9 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
lt_get_version(), lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz(), mac_id,
|
||||
lt_get_board_code(), flash_kib, ram_kib, reset_reason);
|
||||
|
||||
pos = buf_append_str(buf, size, pos, "|Version: ");
|
||||
pos = buf_append_str(buf, size, pos, LT_BANNER_STR + 10);
|
||||
pos = buf_append_str(buf, size, pos, "|Reset Reason: ");
|
||||
pos = buf_append_str(buf, size, pos, reset_reason);
|
||||
pos = buf_append_str(buf, size, pos, "|Chip Name: ");
|
||||
pos = buf_append_str(buf, size, pos, lt_cpu_get_model_name());
|
||||
pos = buf_append_printf(buf, size, pos, "|Version: %s", LT_BANNER_STR + 10);
|
||||
pos = buf_append_printf(buf, size, pos, "|Reset Reason: %s", reset_reason);
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip Name: %s", lt_cpu_get_model_name());
|
||||
pos = buf_append_printf(buf, size, pos, "|Chip ID: 0x%06" PRIX32, mac_id);
|
||||
pos = buf_append_printf(buf, size, pos, "|Flash: %" PRIu32 " KiB", flash_kib);
|
||||
pos = buf_append_printf(buf, size, pos, "|RAM: %" PRIu32 " KiB", ram_kib);
|
||||
|
||||
@@ -162,18 +162,14 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
const char *supply_status =
|
||||
(nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_NORMAL) ? "Normal voltage." : "High voltage.";
|
||||
ESP_LOGD(TAG, "Main supply status: %s", supply_status);
|
||||
pos = buf_append_str(buf, size, pos, "|Main supply status: ");
|
||||
pos = buf_append_str(buf, size, pos, supply_status);
|
||||
pos = buf_append_printf(buf, size, pos, "|Main supply status: %s", supply_status);
|
||||
|
||||
// Regulator stage 0
|
||||
if (nrf_power_mainregstatus_get(NRF_POWER) == NRF_POWER_MAINREGSTATUS_HIGH) {
|
||||
const char *reg0_type = nrf_power_dcdcen_vddh_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
const char *reg0_voltage = regout0_to_str((NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) >> UICR_REGOUT0_VOUT_Pos);
|
||||
ESP_LOGD(TAG, "Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
pos = buf_append_str(buf, size, pos, "|Regulator stage 0: ");
|
||||
pos = buf_append_str(buf, size, pos, reg0_type);
|
||||
pos = buf_append_str(buf, size, pos, ", ");
|
||||
pos = buf_append_str(buf, size, pos, reg0_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: %s, %s", reg0_type, reg0_voltage);
|
||||
#ifdef USE_NRF52_REG0_VOUT
|
||||
if ((NRF_UICR->REGOUT0 & UICR_REGOUT0_VOUT_Msk) >> UICR_REGOUT0_VOUT_Pos != USE_NRF52_REG0_VOUT) {
|
||||
ESP_LOGE(TAG, "Regulator stage 0: expected %s", regout0_to_str(USE_NRF52_REG0_VOUT));
|
||||
@@ -181,14 +177,13 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
#endif
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Regulator stage 0: disabled");
|
||||
pos = buf_append_str(buf, size, pos, "|Regulator stage 0: disabled");
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 0: disabled");
|
||||
}
|
||||
|
||||
// Regulator stage 1
|
||||
const char *reg1_type = nrf_power_dcdcen_get(NRF_POWER) ? "DC/DC" : "LDO";
|
||||
ESP_LOGD(TAG, "Regulator stage 1: %s", reg1_type);
|
||||
pos = buf_append_str(buf, size, pos, "|Regulator stage 1: ");
|
||||
pos = buf_append_str(buf, size, pos, reg1_type);
|
||||
pos = buf_append_printf(buf, size, pos, "|Regulator stage 1: %s", reg1_type);
|
||||
|
||||
// USB power state
|
||||
const char *usb_state;
|
||||
@@ -202,8 +197,7 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
usb_state = "disconnected";
|
||||
}
|
||||
ESP_LOGD(TAG, "USB power state: %s", usb_state);
|
||||
pos = buf_append_str(buf, size, pos, "|USB power state: ");
|
||||
pos = buf_append_str(buf, size, pos, usb_state);
|
||||
pos = buf_append_printf(buf, size, pos, "|USB power state: %s", usb_state);
|
||||
|
||||
// Power-fail comparator
|
||||
bool enabled;
|
||||
@@ -308,18 +302,14 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
break;
|
||||
}
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
pos = buf_append_str(buf, size, pos, "|Power-fail comparator: ");
|
||||
pos = buf_append_str(buf, size, pos, pof_voltage);
|
||||
pos = buf_append_str(buf, size, pos, ", VDDH: ");
|
||||
pos = buf_append_str(buf, size, pos, vddh_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s, VDDH: %s", pof_voltage, vddh_voltage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Power-fail comparator: %s", pof_voltage);
|
||||
pos = buf_append_str(buf, size, pos, "|Power-fail comparator: ");
|
||||
pos = buf_append_str(buf, size, pos, pof_voltage);
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: %s", pof_voltage);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Power-fail comparator: disabled");
|
||||
pos = buf_append_str(buf, size, pos, "|Power-fail comparator: disabled");
|
||||
pos = buf_append_printf(buf, size, pos, "|Power-fail comparator: disabled");
|
||||
}
|
||||
|
||||
auto package = [](uint32_t value) {
|
||||
|
||||
@@ -14,7 +14,6 @@ from esphome.components.esp32 import (
|
||||
VARIANT_ESP32S3,
|
||||
get_esp32_variant,
|
||||
)
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -34,7 +33,6 @@ from esphome.const import (
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_NRF52,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
@@ -193,14 +191,11 @@ def _validate_ex1_wakeup_mode(value):
|
||||
|
||||
|
||||
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
|
||||
if CORE.is_bk72xx:
|
||||
max_duration = core.TimePeriod(hours=36)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
|
||||
elif CORE.using_zephyr:
|
||||
max_duration = core.TimePeriod(days=49)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 49 days on Zephyr")
|
||||
if not CORE.is_bk72xx:
|
||||
return value
|
||||
max_duration = core.TimePeriod(hours=36)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
|
||||
return value
|
||||
|
||||
|
||||
@@ -309,7 +304,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX, PLATFORM_NRF52]),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
|
||||
validate_config,
|
||||
)
|
||||
|
||||
@@ -374,8 +369,6 @@ async def to_code(config):
|
||||
|
||||
if CONF_TOUCH_WAKEUP in config:
|
||||
cg.add(var.set_touch_wakeup(config[CONF_TOUCH_WAKEUP]))
|
||||
if CORE.using_zephyr and "zigbee" not in CORE.loaded_integrations:
|
||||
zephyr_add_prj_conf("POWEROFF", True)
|
||||
|
||||
cg.add_define("USE_DEEP_SLEEP")
|
||||
|
||||
|
||||
@@ -59,8 +59,6 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
lt_deep_sleep_enter();
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::should_teardown_() { return true; }
|
||||
|
||||
} // namespace esphome::deep_sleep
|
||||
|
||||
#endif // USE_BK72XX
|
||||
|
||||
@@ -13,11 +13,7 @@ bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const
|
||||
|
||||
void DeepSleepComponent::setup() {
|
||||
global_has_deep_sleep = true;
|
||||
this->schedule_sleep_();
|
||||
}
|
||||
|
||||
void DeepSleepComponent::schedule_sleep_() {
|
||||
this->next_enter_deep_sleep_ = false;
|
||||
const optional<uint32_t> run_duration = get_run_duration_();
|
||||
if (run_duration.has_value()) {
|
||||
ESP_LOGI(TAG, "Scheduling in %" PRIu32 " ms", *run_duration);
|
||||
@@ -62,17 +58,13 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
|
||||
}
|
||||
|
||||
if (this->should_teardown_()) {
|
||||
App.run_safe_shutdown_hooks();
|
||||
// It's critical to teardown components cleanly for deep sleep to ensure
|
||||
// Home Assistant sees a clean disconnect instead of marking the device unavailable
|
||||
App.teardown_components(TEARDOWN_TIMEOUT_DEEP_SLEEP_MS);
|
||||
App.run_powerdown_hooks();
|
||||
}
|
||||
App.run_safe_shutdown_hooks();
|
||||
// It's critical to teardown components cleanly for deep sleep to ensure
|
||||
// Home Assistant sees a clean disconnect instead of marking the device unavailable
|
||||
App.teardown_components(TEARDOWN_TIMEOUT_DEEP_SLEEP_MS);
|
||||
App.run_powerdown_hooks();
|
||||
|
||||
this->deep_sleep_();
|
||||
this->schedule_sleep_();
|
||||
}
|
||||
|
||||
float DeepSleepComponent::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
@@ -128,8 +129,6 @@ class DeepSleepComponent : public Component {
|
||||
void dump_config_platform_();
|
||||
bool prepare_to_sleep_();
|
||||
void deep_sleep_();
|
||||
void schedule_sleep_();
|
||||
bool should_teardown_();
|
||||
|
||||
#ifdef USE_BK72XX
|
||||
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
|
||||
|
||||
@@ -165,8 +165,6 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::should_teardown_() { return true; }
|
||||
|
||||
} // namespace deep_sleep
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP32
|
||||
|
||||
@@ -18,8 +18,6 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
ESP.deepSleep(this->sleep_duration_.value_or(0)); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::should_teardown_() { return true; }
|
||||
|
||||
} // namespace deep_sleep
|
||||
} // namespace esphome
|
||||
#endif
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#include "deep_sleep_component.h"
|
||||
#ifdef USE_ZEPHYR
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/wake.h"
|
||||
#include <zephyr/sys/poweroff.h>
|
||||
|
||||
namespace esphome::deep_sleep {
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
|
||||
|
||||
void DeepSleepComponent::dump_config_platform_() {}
|
||||
|
||||
bool DeepSleepComponent::prepare_to_sleep_() { return true; }
|
||||
|
||||
void DeepSleepComponent::deep_sleep_() {
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
esphome::internal::wakeable_delay(static_cast<uint32_t>(*this->sleep_duration_ / 1000));
|
||||
} else {
|
||||
#ifndef USE_ZIGBEE
|
||||
// the device can be woken up through one of the following signals:
|
||||
// - The DETECT signal, optionally generated by the GPIO peripheral.
|
||||
// - The ANADETECT signal, optionally generated by the LPCOMP module.
|
||||
// - The SENSE signal, optionally generated by the NFC module to wake-on-field.
|
||||
// - Detecting a valid USB voltage on the VBUS pin (VBUS,DETECT).
|
||||
// - A reset.
|
||||
//
|
||||
// The system is reset when it wakes up from System OFF mode.
|
||||
sys_poweroff();
|
||||
#else
|
||||
esphome::internal::wakeable_delay(UINT32_MAX);
|
||||
#endif
|
||||
}
|
||||
const bool woke = esphome::wake_request_take();
|
||||
if (woke) {
|
||||
ESP_LOGD(TAG, "Woken up by another thread");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Timeout expired (normal sleep)");
|
||||
}
|
||||
}
|
||||
|
||||
bool DeepSleepComponent::should_teardown_() {
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
#ifdef USE_ZIGBEE
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome::deep_sleep
|
||||
|
||||
#endif
|
||||
@@ -29,7 +29,7 @@ class DemoAlarmControlPanel : public AlarmControlPanel, public Component {
|
||||
protected:
|
||||
void control(const AlarmControlPanelCall &call) override {
|
||||
auto state = call.get_state().value_or(ACP_STATE_DISARMED);
|
||||
const auto &code = call.get_code();
|
||||
auto code = call.get_code();
|
||||
switch (state) {
|
||||
case ACP_STATE_ARMED_AWAY:
|
||||
if (this->get_requires_code_to_arm()) {
|
||||
|
||||
@@ -104,9 +104,8 @@ int8_t CircularCommandQueue::enqueue(std::unique_ptr<Command> cmd) {
|
||||
if (this->is_full()) {
|
||||
ESP_LOGE(TAG, "Command queue is full");
|
||||
return -1;
|
||||
} else if (this->is_empty()) {
|
||||
} else if (this->is_empty())
|
||||
front_++;
|
||||
}
|
||||
rear_ = (rear_ + 1) % COMMAND_QUEUE_SIZE;
|
||||
commands_[rear_] = std::move(cmd); // Transfer ownership using std::move
|
||||
return 1;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import logging
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_RECEIVE_TIMEOUT,
|
||||
CONF_RX_BUFFER_SIZE,
|
||||
CONF_UART_ID,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from esphome.const import CONF_ID, CONF_RECEIVE_TIMEOUT, CONF_UART_ID
|
||||
|
||||
CODEOWNERS = ["@glmnet", "@PolarGoose"]
|
||||
|
||||
@@ -32,7 +21,8 @@ CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length"
|
||||
CONF_REQUEST_INTERVAL = "request_interval"
|
||||
CONF_REQUEST_PIN = "request_pin"
|
||||
|
||||
dsmr_ns = cg.esphome_ns.namespace("dsmr")
|
||||
# Hack to prevent compile error due to ambiguity with lib namespace
|
||||
dsmr_ns = cg.esphome_ns.namespace("esphome::dsmr")
|
||||
Dsmr = dsmr_ns.class_("Dsmr", cg.Component, uart.UARTDevice)
|
||||
|
||||
|
||||
@@ -64,47 +54,24 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
async def to_code(config):
|
||||
uart_component = await cg.get_variable(config[CONF_UART_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], uart_component, config[CONF_CRC_CHECK])
|
||||
cg.add(var.set_max_telegram_length(config[CONF_MAX_TELEGRAM_LENGTH]))
|
||||
if CONF_DECRYPTION_KEY in config:
|
||||
cg.add(var.set_decryption_key(config[CONF_DECRYPTION_KEY]))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_REQUEST_PIN in config:
|
||||
request_pin = await cg.gpio_pin_expression(config[CONF_REQUEST_PIN])
|
||||
else:
|
||||
request_pin = cg.nullptr
|
||||
decryption_key = config.get(CONF_DECRYPTION_KEY)
|
||||
if decryption_key is None:
|
||||
decryption_key = cg.nullptr
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
uart_component,
|
||||
config[CONF_CRC_CHECK],
|
||||
config[CONF_MAX_TELEGRAM_LENGTH],
|
||||
config[CONF_REQUEST_INTERVAL].total_milliseconds,
|
||||
config[CONF_RECEIVE_TIMEOUT].total_milliseconds,
|
||||
request_pin,
|
||||
decryption_key,
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_request_pin(request_pin))
|
||||
cg.add(var.set_request_interval(config[CONF_REQUEST_INTERVAL].total_milliseconds))
|
||||
cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds))
|
||||
|
||||
cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID]))
|
||||
cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID]))
|
||||
cg.add_build_flag("-DDSMR_THERMAL_MBUS_ID=" + str(config[CONF_THERMAL_MBUS_ID]))
|
||||
|
||||
cg.add_library("esphome/dsmr_parser", "1.4.0")
|
||||
# DSMR Parser
|
||||
cg.add_library("esphome/dsmr_parser", "1.1.0")
|
||||
|
||||
|
||||
def final_validate(config: ConfigType) -> ConfigType:
|
||||
full_config = fv.full_config.get()
|
||||
|
||||
for uart_conf in full_config["uart"]:
|
||||
if uart_conf[CONF_ID] == config[CONF_UART_ID]:
|
||||
rx_buffer_size = uart_conf[CONF_RX_BUFFER_SIZE]
|
||||
if rx_buffer_size < 1500:
|
||||
_LOGGER.warning(
|
||||
"UART '%s' rx_buffer_size should be bigger than 1500 bytes to avoid packet losses (currently %d bytes).",
|
||||
config[CONF_UART_ID],
|
||||
rx_buffer_size,
|
||||
)
|
||||
break
|
||||
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = final_validate
|
||||
# Crypto
|
||||
cg.add_library("polargoose/Crypto-no-arduino", "0.4.0")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user