mirror of
https://github.com/esphome/esphome.git
synced 2026-01-29 16:02:13 -07:00
Compare commits
44 Commits
mqtt_enum_
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6de2049076 | ||
|
|
cd43f8474e | ||
|
|
ecc0b366b3 | ||
|
|
6a17db8857 | ||
|
|
0843ec6ae8 | ||
|
|
74c84c8747 | ||
|
|
3e9a6c582e | ||
|
|
084113926c | ||
|
|
a5f60750c2 | ||
|
|
a382383d83 | ||
|
|
03cfd87b16 | ||
|
|
6d8294c2d3 | ||
|
|
6a3205f4db | ||
|
|
6f22509883 | ||
|
|
455ade0dca | ||
|
|
87fcfc9d76 | ||
|
|
d86048cc2d | ||
|
|
e1355de4cb | ||
|
|
7385c4cf3d | ||
|
|
3bd6ec4ec7 | ||
|
|
051604f284 | ||
|
|
10dfd95ff2 | ||
|
|
22e0a8ce2e | ||
|
|
b4f63fd992 | ||
|
|
ded835ab63 | ||
|
|
73a249c075 | ||
|
|
fe6f27c526 | ||
|
|
f73c539ea7 | ||
|
|
f87aa384d0 | ||
|
|
f9687a2a31 | ||
|
|
f084d320fc | ||
|
|
f93382445e | ||
|
|
463363a08d | ||
|
|
a0790f926e | ||
|
|
ca59ab8f37 | ||
|
|
b2474c6de9 | ||
|
|
3aaf10b6a8 | ||
|
|
33f545a8e3 | ||
|
|
d056e1040b | ||
|
|
75a78b2bf3 | ||
|
|
cd6314dc96 | ||
|
|
f91bffff9a | ||
|
|
5cbe9af485 | ||
|
|
a7fbecb25c |
@@ -1 +1 @@
|
|||||||
d565b0589e35e692b5f2fc0c14723a99595b4828a3a3ef96c442e86a23176c00
|
cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab
|
||||||
|
|||||||
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 }}
|
python-version: ${{ inputs.python-version }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
|
|||||||
38
.github/scripts/auto-label-pr/constants.js
vendored
Normal file
38
.github/scripts/auto-label-pr/constants.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Constants and markers for PR auto-labeling
|
||||||
|
module.exports = {
|
||||||
|
BOT_COMMENT_MARKER: '<!-- auto-label-pr-bot -->',
|
||||||
|
CODEOWNERS_MARKER: '<!-- codeowners-request -->',
|
||||||
|
TOO_BIG_MARKER: '<!-- too-big-request -->',
|
||||||
|
DEPRECATED_COMPONENT_MARKER: '<!-- deprecated-component-request -->',
|
||||||
|
|
||||||
|
MANAGED_LABELS: [
|
||||||
|
'new-component',
|
||||||
|
'new-platform',
|
||||||
|
'new-target-platform',
|
||||||
|
'merging-to-release',
|
||||||
|
'merging-to-beta',
|
||||||
|
'chained-pr',
|
||||||
|
'core',
|
||||||
|
'small-pr',
|
||||||
|
'dashboard',
|
||||||
|
'github-actions',
|
||||||
|
'by-code-owner',
|
||||||
|
'has-tests',
|
||||||
|
'needs-tests',
|
||||||
|
'needs-docs',
|
||||||
|
'needs-codeowners',
|
||||||
|
'too-big',
|
||||||
|
'labeller-recheck',
|
||||||
|
'bugfix',
|
||||||
|
'new-feature',
|
||||||
|
'breaking-change',
|
||||||
|
'developer-breaking-change',
|
||||||
|
'code-quality',
|
||||||
|
'deprecated-component'
|
||||||
|
],
|
||||||
|
|
||||||
|
DOCS_PR_PATTERNS: [
|
||||||
|
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
||||||
|
/esphome\/esphome-docs#\d+/
|
||||||
|
]
|
||||||
|
};
|
||||||
373
.github/scripts/auto-label-pr/detectors.js
vendored
Normal file
373
.github/scripts/auto-label-pr/detectors.js
vendored
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const { DOCS_PR_PATTERNS } = require('./constants');
|
||||||
|
|
||||||
|
// Strategy: Merge branch detection
|
||||||
|
async function detectMergeBranch(context) {
|
||||||
|
const labels = new Set();
|
||||||
|
const baseRef = context.payload.pull_request.base.ref;
|
||||||
|
|
||||||
|
if (baseRef === 'release') {
|
||||||
|
labels.add('merging-to-release');
|
||||||
|
} else if (baseRef === 'beta') {
|
||||||
|
labels.add('merging-to-beta');
|
||||||
|
} else if (baseRef !== 'dev') {
|
||||||
|
labels.add('chained-pr');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Component and platform labeling
|
||||||
|
async function detectComponentPlatforms(changedFiles, apiData) {
|
||||||
|
const labels = new Set();
|
||||||
|
const componentRegex = /^esphome\/components\/([^\/]+)\//;
|
||||||
|
const targetPlatformRegex = new RegExp(`^esphome\/components\/(${apiData.targetPlatforms.join('|')})/`);
|
||||||
|
|
||||||
|
for (const file of changedFiles) {
|
||||||
|
const componentMatch = file.match(componentRegex);
|
||||||
|
if (componentMatch) {
|
||||||
|
labels.add(`component: ${componentMatch[1]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformMatch = file.match(targetPlatformRegex);
|
||||||
|
if (platformMatch) {
|
||||||
|
labels.add(`platform: ${platformMatch[1]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: New component detection
|
||||||
|
async function detectNewComponents(prFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
||||||
|
|
||||||
|
for (const file of addedFiles) {
|
||||||
|
const componentMatch = file.match(/^esphome\/components\/([^\/]+)\/__init__\.py$/);
|
||||||
|
if (componentMatch) {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
|
if (content.includes('IS_TARGET_PLATFORM = True')) {
|
||||||
|
labels.add('new-target-platform');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to read content of ${file}:`, error.message);
|
||||||
|
}
|
||||||
|
labels.add('new-component');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: New platform detection
|
||||||
|
async function detectNewPlatforms(prFiles, apiData) {
|
||||||
|
const labels = new Set();
|
||||||
|
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
||||||
|
|
||||||
|
for (const file of addedFiles) {
|
||||||
|
const platformFileMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\.py$/);
|
||||||
|
if (platformFileMatch) {
|
||||||
|
const [, component, platform] = platformFileMatch;
|
||||||
|
if (apiData.platformComponents.includes(platform)) {
|
||||||
|
labels.add('new-platform');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformDirMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/);
|
||||||
|
if (platformDirMatch) {
|
||||||
|
const [, component, platform] = platformDirMatch;
|
||||||
|
if (apiData.platformComponents.includes(platform)) {
|
||||||
|
labels.add('new-platform');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Core files detection
|
||||||
|
async function detectCoreChanges(changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const coreFiles = changedFiles.filter(file =>
|
||||||
|
file.startsWith('esphome/core/') ||
|
||||||
|
(file.startsWith('esphome/') && file.split('/').length === 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (coreFiles.length > 0) {
|
||||||
|
labels.add('core');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: PR size detection
|
||||||
|
async function detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, TOO_BIG_THRESHOLD) {
|
||||||
|
const labels = new Set();
|
||||||
|
|
||||||
|
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||||
|
labels.add('small-pr');
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAdditions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
|
const testDeletions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
|
||||||
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||||
|
|
||||||
|
// Don't add too-big if mega-pr label is already present
|
||||||
|
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
||||||
|
labels.add('too-big');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Dashboard changes
|
||||||
|
async function detectDashboardChanges(changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const dashboardFiles = changedFiles.filter(file =>
|
||||||
|
file.startsWith('esphome/dashboard/') ||
|
||||||
|
file.startsWith('esphome/components/dashboard_import/')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dashboardFiles.length > 0) {
|
||||||
|
labels.add('dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: GitHub Actions changes
|
||||||
|
async function detectGitHubActionsChanges(changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const githubActionsFiles = changedFiles.filter(file =>
|
||||||
|
file.startsWith('.github/workflows/')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (githubActionsFiles.length > 0) {
|
||||||
|
labels.add('github-actions');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Code owner detection
|
||||||
|
async function detectCodeOwner(github, context, changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: codeownersFile } = await github.rest.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: 'CODEOWNERS',
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
||||||
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
|
const codeownersLines = codeownersContent.split('\n')
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(line => line && !line.startsWith('#'));
|
||||||
|
|
||||||
|
const codeownersRegexes = codeownersLines.map(line => {
|
||||||
|
const parts = line.split(/\s+/);
|
||||||
|
const pattern = parts[0];
|
||||||
|
const owners = parts.slice(1);
|
||||||
|
|
||||||
|
let regex;
|
||||||
|
if (pattern.endsWith('*')) {
|
||||||
|
const dir = pattern.slice(0, -1);
|
||||||
|
regex = new RegExp(`^${dir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
|
||||||
|
} else if (pattern.includes('*')) {
|
||||||
|
// First escape all regex special chars except *, then replace * with .*
|
||||||
|
const regexPattern = pattern
|
||||||
|
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
||||||
|
.replace(/\*/g, '.*');
|
||||||
|
regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
} else {
|
||||||
|
regex = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { regex, owners };
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const file of changedFiles) {
|
||||||
|
for (const { regex, owners } of codeownersRegexes) {
|
||||||
|
if (regex.test(file) && owners.some(owner => owner === `@${prAuthor}`)) {
|
||||||
|
labels.add('by-code-owner');
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to read or parse CODEOWNERS file:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Test detection
|
||||||
|
async function detectTests(changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const testFiles = changedFiles.filter(file => file.startsWith('tests/'));
|
||||||
|
|
||||||
|
if (testFiles.length > 0) {
|
||||||
|
labels.add('has-tests');
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: PR Template Checkbox detection
|
||||||
|
async function detectPRTemplateCheckboxes(context) {
|
||||||
|
const labels = new Set();
|
||||||
|
const prBody = context.payload.pull_request.body || '';
|
||||||
|
|
||||||
|
console.log('Checking PR template checkboxes...');
|
||||||
|
|
||||||
|
// Check for checked checkboxes in the "Types of changes" section
|
||||||
|
const checkboxPatterns = [
|
||||||
|
{ pattern: /- \[x\] Bugfix \(non-breaking change which fixes an issue\)/i, label: 'bugfix' },
|
||||||
|
{ pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' },
|
||||||
|
{ pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' },
|
||||||
|
{ pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' },
|
||||||
|
{ pattern: /- \[x\] Code quality improvements to existing code or addition of tests/i, label: 'code-quality' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { pattern, label } of checkboxPatterns) {
|
||||||
|
if (pattern.test(prBody)) {
|
||||||
|
console.log(`Found checked checkbox for: ${label}`);
|
||||||
|
labels.add(label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Deprecated component detection
|
||||||
|
async function detectDeprecatedComponents(github, context, changedFiles) {
|
||||||
|
const labels = new Set();
|
||||||
|
const deprecatedInfo = [];
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
|
||||||
|
// Compile regex once for better performance
|
||||||
|
const componentFileRegex = /^esphome\/components\/([^\/]+)\//;
|
||||||
|
|
||||||
|
// Get files that are modified or added in components directory
|
||||||
|
const componentFiles = changedFiles.filter(file => componentFileRegex.test(file));
|
||||||
|
|
||||||
|
if (componentFiles.length === 0) {
|
||||||
|
return { labels, deprecatedInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract unique component names using the same regex
|
||||||
|
const components = new Set();
|
||||||
|
for (const file of componentFiles) {
|
||||||
|
const match = file.match(componentFileRegex);
|
||||||
|
if (match) {
|
||||||
|
components.add(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get PR head to fetch files from the PR branch
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
|
||||||
|
// Check each component's __init__.py for DEPRECATED_COMPONENT constant
|
||||||
|
for (const component of components) {
|
||||||
|
const initFile = `esphome/components/${component}/__init__.py`;
|
||||||
|
try {
|
||||||
|
// Fetch file content from PR head using GitHub API
|
||||||
|
const { data: fileData } = await github.rest.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: initFile,
|
||||||
|
ref: `refs/pull/${prNumber}/head`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Decode base64 content
|
||||||
|
const content = Buffer.from(fileData.content, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
// Look for DEPRECATED_COMPONENT = "message" or DEPRECATED_COMPONENT = 'message'
|
||||||
|
// Support single quotes, double quotes, and triple quotes (for multiline)
|
||||||
|
const doubleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*"""([\s\S]*?)"""/s) ||
|
||||||
|
content.match(/DEPRECATED_COMPONENT\s*=\s*"((?:[^"\\]|\\.)*)"/);
|
||||||
|
const singleQuoteMatch = content.match(/DEPRECATED_COMPONENT\s*=\s*'''([\s\S]*?)'''/s) ||
|
||||||
|
content.match(/DEPRECATED_COMPONENT\s*=\s*'((?:[^'\\]|\\.)*)'/);
|
||||||
|
const deprecatedMatch = doubleQuoteMatch || singleQuoteMatch;
|
||||||
|
|
||||||
|
if (deprecatedMatch) {
|
||||||
|
labels.add('deprecated-component');
|
||||||
|
deprecatedInfo.push({
|
||||||
|
component: component,
|
||||||
|
message: deprecatedMatch[1].trim()
|
||||||
|
});
|
||||||
|
console.log(`Found deprecated component: ${component}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Only log if it's not a simple "file not found" error (404)
|
||||||
|
if (error.status !== 404) {
|
||||||
|
console.log(`Error reading ${initFile}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { labels, deprecatedInfo };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy: Requirements detection
|
||||||
|
async function detectRequirements(allLabels, prFiles, context) {
|
||||||
|
const labels = new Set();
|
||||||
|
|
||||||
|
// Check for missing tests
|
||||||
|
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
||||||
|
labels.add('needs-tests');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for missing docs
|
||||||
|
if (allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) {
|
||||||
|
const prBody = context.payload.pull_request.body || '';
|
||||||
|
const hasDocsLink = DOCS_PR_PATTERNS.some(pattern => pattern.test(prBody));
|
||||||
|
|
||||||
|
if (!hasDocsLink) {
|
||||||
|
labels.add('needs-docs');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for missing CODEOWNERS
|
||||||
|
if (allLabels.has('new-component')) {
|
||||||
|
const codeownersModified = prFiles.some(file =>
|
||||||
|
file.filename === 'CODEOWNERS' &&
|
||||||
|
(file.status === 'modified' || file.status === 'added') &&
|
||||||
|
(file.additions || 0) > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!codeownersModified) {
|
||||||
|
labels.add('needs-codeowners');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
detectMergeBranch,
|
||||||
|
detectComponentPlatforms,
|
||||||
|
detectNewComponents,
|
||||||
|
detectNewPlatforms,
|
||||||
|
detectCoreChanges,
|
||||||
|
detectPRSize,
|
||||||
|
detectDashboardChanges,
|
||||||
|
detectGitHubActionsChanges,
|
||||||
|
detectCodeOwner,
|
||||||
|
detectTests,
|
||||||
|
detectPRTemplateCheckboxes,
|
||||||
|
detectDeprecatedComponents,
|
||||||
|
detectRequirements
|
||||||
|
};
|
||||||
187
.github/scripts/auto-label-pr/index.js
vendored
Normal file
187
.github/scripts/auto-label-pr/index.js
vendored
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
const { MANAGED_LABELS } = require('./constants');
|
||||||
|
const {
|
||||||
|
detectMergeBranch,
|
||||||
|
detectComponentPlatforms,
|
||||||
|
detectNewComponents,
|
||||||
|
detectNewPlatforms,
|
||||||
|
detectCoreChanges,
|
||||||
|
detectPRSize,
|
||||||
|
detectDashboardChanges,
|
||||||
|
detectGitHubActionsChanges,
|
||||||
|
detectCodeOwner,
|
||||||
|
detectTests,
|
||||||
|
detectPRTemplateCheckboxes,
|
||||||
|
detectDeprecatedComponents,
|
||||||
|
detectRequirements
|
||||||
|
} = require('./detectors');
|
||||||
|
const { handleReviews } = require('./reviews');
|
||||||
|
const { applyLabels, removeOldLabels } = require('./labels');
|
||||||
|
|
||||||
|
// Fetch API data
|
||||||
|
async function fetchApiData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://data.esphome.io/components.json');
|
||||||
|
const componentsData = await response.json();
|
||||||
|
return {
|
||||||
|
targetPlatforms: componentsData.target_platforms || [],
|
||||||
|
platformComponents: componentsData.platform_components || []
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to fetch components data from API:', error.message);
|
||||||
|
return { targetPlatforms: [], platformComponents: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = async ({ github, context }) => {
|
||||||
|
// Environment variables
|
||||||
|
const SMALL_PR_THRESHOLD = parseInt(process.env.SMALL_PR_THRESHOLD);
|
||||||
|
const MAX_LABELS = parseInt(process.env.MAX_LABELS);
|
||||||
|
const TOO_BIG_THRESHOLD = parseInt(process.env.TOO_BIG_THRESHOLD);
|
||||||
|
const COMPONENT_LABEL_THRESHOLD = parseInt(process.env.COMPONENT_LABEL_THRESHOLD);
|
||||||
|
|
||||||
|
// Global state
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const pr_number = context.issue.number;
|
||||||
|
|
||||||
|
// Get current labels and PR data
|
||||||
|
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number
|
||||||
|
});
|
||||||
|
const currentLabels = currentLabelsData.map(label => label.name);
|
||||||
|
const managedLabels = currentLabels.filter(label =>
|
||||||
|
label.startsWith('component: ') || MANAGED_LABELS.includes(label)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for mega-PR early - if present, skip most automatic labeling
|
||||||
|
const isMegaPR = currentLabels.includes('mega-pr');
|
||||||
|
|
||||||
|
// Get all PR files with automatic pagination
|
||||||
|
const prFiles = await github.paginate(
|
||||||
|
github.rest.pulls.listFiles,
|
||||||
|
{
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate data from PR files
|
||||||
|
const changedFiles = prFiles.map(file => file.filename);
|
||||||
|
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
|
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
const totalChanges = totalAdditions + totalDeletions;
|
||||||
|
|
||||||
|
console.log('Current labels:', currentLabels.join(', '));
|
||||||
|
console.log('Changed files:', changedFiles.length);
|
||||||
|
console.log('Total changes:', totalChanges);
|
||||||
|
if (isMegaPR) {
|
||||||
|
console.log('Mega-PR detected - applying limited labeling logic');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch API data
|
||||||
|
const apiData = await fetchApiData();
|
||||||
|
const baseRef = context.payload.pull_request.base.ref;
|
||||||
|
|
||||||
|
// Early exit for release and beta branches only
|
||||||
|
if (baseRef === 'release' || baseRef === 'beta') {
|
||||||
|
const branchLabels = await detectMergeBranch(context);
|
||||||
|
const finalLabels = Array.from(branchLabels);
|
||||||
|
|
||||||
|
console.log('Computed labels (merge branch only):', finalLabels.join(', '));
|
||||||
|
|
||||||
|
// Apply labels
|
||||||
|
await applyLabels(github, context, finalLabels);
|
||||||
|
|
||||||
|
// Remove old managed labels
|
||||||
|
await removeOldLabels(github, context, managedLabels, finalLabels);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all strategies
|
||||||
|
const [
|
||||||
|
branchLabels,
|
||||||
|
componentLabels,
|
||||||
|
newComponentLabels,
|
||||||
|
newPlatformLabels,
|
||||||
|
coreLabels,
|
||||||
|
sizeLabels,
|
||||||
|
dashboardLabels,
|
||||||
|
actionsLabels,
|
||||||
|
codeOwnerLabels,
|
||||||
|
testLabels,
|
||||||
|
checkboxLabels,
|
||||||
|
deprecatedResult
|
||||||
|
] = await Promise.all([
|
||||||
|
detectMergeBranch(context),
|
||||||
|
detectComponentPlatforms(changedFiles, apiData),
|
||||||
|
detectNewComponents(prFiles),
|
||||||
|
detectNewPlatforms(prFiles, apiData),
|
||||||
|
detectCoreChanges(changedFiles),
|
||||||
|
detectPRSize(prFiles, totalAdditions, totalDeletions, totalChanges, isMegaPR, SMALL_PR_THRESHOLD, TOO_BIG_THRESHOLD),
|
||||||
|
detectDashboardChanges(changedFiles),
|
||||||
|
detectGitHubActionsChanges(changedFiles),
|
||||||
|
detectCodeOwner(github, context, changedFiles),
|
||||||
|
detectTests(changedFiles),
|
||||||
|
detectPRTemplateCheckboxes(context),
|
||||||
|
detectDeprecatedComponents(github, context, changedFiles)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Extract deprecated component info
|
||||||
|
const deprecatedLabels = deprecatedResult.labels;
|
||||||
|
const deprecatedInfo = deprecatedResult.deprecatedInfo;
|
||||||
|
|
||||||
|
// Combine all labels
|
||||||
|
const allLabels = new Set([
|
||||||
|
...branchLabels,
|
||||||
|
...componentLabels,
|
||||||
|
...newComponentLabels,
|
||||||
|
...newPlatformLabels,
|
||||||
|
...coreLabels,
|
||||||
|
...sizeLabels,
|
||||||
|
...dashboardLabels,
|
||||||
|
...actionsLabels,
|
||||||
|
...codeOwnerLabels,
|
||||||
|
...testLabels,
|
||||||
|
...checkboxLabels,
|
||||||
|
...deprecatedLabels
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Detect requirements based on all other labels
|
||||||
|
const requirementLabels = await detectRequirements(allLabels, prFiles, context);
|
||||||
|
for (const label of requirementLabels) {
|
||||||
|
allLabels.add(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalLabels = Array.from(allLabels);
|
||||||
|
|
||||||
|
// For mega-PRs, exclude component labels if there are too many
|
||||||
|
if (isMegaPR) {
|
||||||
|
const componentLabels = finalLabels.filter(label => label.startsWith('component: '));
|
||||||
|
if (componentLabels.length > COMPONENT_LABEL_THRESHOLD) {
|
||||||
|
finalLabels = finalLabels.filter(label => !label.startsWith('component: '));
|
||||||
|
console.log(`Mega-PR detected - excluding ${componentLabels.length} component labels (threshold: ${COMPONENT_LABEL_THRESHOLD})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle too many labels (only for non-mega PRs)
|
||||||
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
|
const originalLabelCount = finalLabels.length;
|
||||||
|
|
||||||
|
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
|
||||||
|
finalLabels = ['too-big'];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Computed labels:', finalLabels.join(', '));
|
||||||
|
|
||||||
|
// Handle reviews
|
||||||
|
await handleReviews(github, context, finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, MAX_LABELS, TOO_BIG_THRESHOLD);
|
||||||
|
|
||||||
|
// Apply labels
|
||||||
|
await applyLabels(github, context, finalLabels);
|
||||||
|
|
||||||
|
// Remove old managed labels
|
||||||
|
await removeOldLabels(github, context, managedLabels, finalLabels);
|
||||||
|
};
|
||||||
41
.github/scripts/auto-label-pr/labels.js
vendored
Normal file
41
.github/scripts/auto-label-pr/labels.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Apply labels to PR
|
||||||
|
async function applyLabels(github, context, finalLabels) {
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const pr_number = context.issue.number;
|
||||||
|
|
||||||
|
if (finalLabels.length > 0) {
|
||||||
|
console.log(`Adding labels: ${finalLabels.join(', ')}`);
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number,
|
||||||
|
labels: finalLabels
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old managed labels
|
||||||
|
async function removeOldLabels(github, context, managedLabels, finalLabels) {
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const pr_number = context.issue.number;
|
||||||
|
|
||||||
|
const labelsToRemove = managedLabels.filter(label => !finalLabels.includes(label));
|
||||||
|
for (const label of labelsToRemove) {
|
||||||
|
console.log(`Removing label: ${label}`);
|
||||||
|
try {
|
||||||
|
await github.rest.issues.removeLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number,
|
||||||
|
name: label
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to remove label ${label}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
applyLabels,
|
||||||
|
removeOldLabels
|
||||||
|
};
|
||||||
141
.github/scripts/auto-label-pr/reviews.js
vendored
Normal file
141
.github/scripts/auto-label-pr/reviews.js
vendored
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
const {
|
||||||
|
BOT_COMMENT_MARKER,
|
||||||
|
CODEOWNERS_MARKER,
|
||||||
|
TOO_BIG_MARKER,
|
||||||
|
DEPRECATED_COMPONENT_MARKER
|
||||||
|
} = require('./constants');
|
||||||
|
|
||||||
|
// Generate review messages
|
||||||
|
function generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, prAuthor, MAX_LABELS, TOO_BIG_THRESHOLD) {
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
// Deprecated component message
|
||||||
|
if (finalLabels.includes('deprecated-component') && deprecatedInfo && deprecatedInfo.length > 0) {
|
||||||
|
let message = `${DEPRECATED_COMPONENT_MARKER}\n### ⚠️ Deprecated Component\n\n`;
|
||||||
|
message += `Hey there @${prAuthor},\n`;
|
||||||
|
message += `This PR modifies one or more deprecated components. Please be aware:\n\n`;
|
||||||
|
|
||||||
|
for (const info of deprecatedInfo) {
|
||||||
|
message += `#### Component: \`${info.component}\`\n`;
|
||||||
|
message += `${info.message}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `Consider migrating to the recommended alternative if applicable.`;
|
||||||
|
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Too big message
|
||||||
|
if (finalLabels.includes('too-big')) {
|
||||||
|
const testAdditions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
||||||
|
const testDeletions = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
||||||
|
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
||||||
|
|
||||||
|
const tooManyLabels = originalLabelCount > MAX_LABELS;
|
||||||
|
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
||||||
|
|
||||||
|
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
||||||
|
|
||||||
|
if (tooManyLabels && tooManyChanges) {
|
||||||
|
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
|
||||||
|
} else if (tooManyLabels) {
|
||||||
|
message += `This PR affects ${originalLabelCount} different components/areas.`;
|
||||||
|
} else {
|
||||||
|
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CODEOWNERS message
|
||||||
|
if (finalLabels.includes('needs-codeowners')) {
|
||||||
|
const message = `${CODEOWNERS_MARKER}\n### 👥 Code Ownership\n\n` +
|
||||||
|
`Hey there @${prAuthor},\n` +
|
||||||
|
`Thanks for submitting this pull request! Can you add yourself as a codeowner for this integration? ` +
|
||||||
|
`This way we can notify you if a bug report for this integration is reported.\n\n` +
|
||||||
|
`In \`__init__.py\` of the integration, please add:\n\n` +
|
||||||
|
`\`\`\`python\nCODEOWNERS = ["@${prAuthor}"]\n\`\`\`\n\n` +
|
||||||
|
`And run \`script/build_codeowners.py\``;
|
||||||
|
|
||||||
|
messages.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle reviews
|
||||||
|
async function handleReviews(github, context, finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, MAX_LABELS, TOO_BIG_THRESHOLD) {
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const pr_number = context.issue.number;
|
||||||
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
|
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount, deprecatedInfo, prFiles, totalAdditions, totalDeletions, prAuthor, MAX_LABELS, TOO_BIG_THRESHOLD);
|
||||||
|
const hasReviewableLabels = finalLabels.some(label =>
|
||||||
|
['too-big', 'needs-codeowners', 'deprecated-component'].includes(label)
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
const botReviews = reviews.filter(review =>
|
||||||
|
review.user.type === 'Bot' &&
|
||||||
|
review.state === 'CHANGES_REQUESTED' &&
|
||||||
|
review.body && review.body.includes(BOT_COMMENT_MARKER)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasReviewableLabels) {
|
||||||
|
const reviewBody = `${BOT_COMMENT_MARKER}\n\n${reviewMessages.join('\n\n---\n\n')}`;
|
||||||
|
|
||||||
|
if (botReviews.length > 0) {
|
||||||
|
// Update existing review
|
||||||
|
await github.rest.pulls.updateReview({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number,
|
||||||
|
review_id: botReviews[0].id,
|
||||||
|
body: reviewBody
|
||||||
|
});
|
||||||
|
console.log('Updated existing bot review');
|
||||||
|
} else {
|
||||||
|
// Create new review
|
||||||
|
await github.rest.pulls.createReview({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number,
|
||||||
|
body: reviewBody,
|
||||||
|
event: 'REQUEST_CHANGES'
|
||||||
|
});
|
||||||
|
console.log('Created new bot review');
|
||||||
|
}
|
||||||
|
} else if (botReviews.length > 0) {
|
||||||
|
// Dismiss existing reviews
|
||||||
|
for (const review of botReviews) {
|
||||||
|
try {
|
||||||
|
await github.rest.pulls.dismissReview({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number,
|
||||||
|
review_id: review.id,
|
||||||
|
message: 'Review dismissed: All requirements have been met'
|
||||||
|
});
|
||||||
|
console.log(`Dismissed bot review ${review.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to dismiss review ${review.id}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
handleReviews
|
||||||
|
};
|
||||||
632
.github/workflows/auto-label-pr.yml
vendored
632
.github/workflows/auto-label-pr.yml
vendored
@@ -36,633 +36,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const script = require('./.github/scripts/auto-label-pr/index.js');
|
||||||
|
await script({ github, context });
|
||||||
// Constants
|
|
||||||
const SMALL_PR_THRESHOLD = parseInt('${{ env.SMALL_PR_THRESHOLD }}');
|
|
||||||
const MAX_LABELS = parseInt('${{ env.MAX_LABELS }}');
|
|
||||||
const TOO_BIG_THRESHOLD = parseInt('${{ env.TOO_BIG_THRESHOLD }}');
|
|
||||||
const COMPONENT_LABEL_THRESHOLD = parseInt('${{ env.COMPONENT_LABEL_THRESHOLD }}');
|
|
||||||
const BOT_COMMENT_MARKER = '<!-- auto-label-pr-bot -->';
|
|
||||||
const CODEOWNERS_MARKER = '<!-- codeowners-request -->';
|
|
||||||
const TOO_BIG_MARKER = '<!-- too-big-request -->';
|
|
||||||
|
|
||||||
const MANAGED_LABELS = [
|
|
||||||
'new-component',
|
|
||||||
'new-platform',
|
|
||||||
'new-target-platform',
|
|
||||||
'merging-to-release',
|
|
||||||
'merging-to-beta',
|
|
||||||
'chained-pr',
|
|
||||||
'core',
|
|
||||||
'small-pr',
|
|
||||||
'dashboard',
|
|
||||||
'github-actions',
|
|
||||||
'by-code-owner',
|
|
||||||
'has-tests',
|
|
||||||
'needs-tests',
|
|
||||||
'needs-docs',
|
|
||||||
'needs-codeowners',
|
|
||||||
'too-big',
|
|
||||||
'labeller-recheck',
|
|
||||||
'bugfix',
|
|
||||||
'new-feature',
|
|
||||||
'breaking-change',
|
|
||||||
'developer-breaking-change',
|
|
||||||
'code-quality'
|
|
||||||
];
|
|
||||||
|
|
||||||
const DOCS_PR_PATTERNS = [
|
|
||||||
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
|
||||||
/esphome\/esphome-docs#\d+/
|
|
||||||
];
|
|
||||||
|
|
||||||
// Global state
|
|
||||||
const { owner, repo } = context.repo;
|
|
||||||
const pr_number = context.issue.number;
|
|
||||||
|
|
||||||
// Get current labels and PR data
|
|
||||||
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr_number
|
|
||||||
});
|
|
||||||
const currentLabels = currentLabelsData.map(label => label.name);
|
|
||||||
const managedLabels = currentLabels.filter(label =>
|
|
||||||
label.startsWith('component: ') || MANAGED_LABELS.includes(label)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for mega-PR early - if present, skip most automatic labeling
|
|
||||||
const isMegaPR = currentLabels.includes('mega-pr');
|
|
||||||
|
|
||||||
// Get all PR files with automatic pagination
|
|
||||||
const prFiles = await github.paginate(
|
|
||||||
github.rest.pulls.listFiles,
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate data from PR files
|
|
||||||
const changedFiles = prFiles.map(file => file.filename);
|
|
||||||
const totalAdditions = prFiles.reduce((sum, file) => sum + (file.additions || 0), 0);
|
|
||||||
const totalDeletions = prFiles.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
const totalChanges = totalAdditions + totalDeletions;
|
|
||||||
|
|
||||||
console.log('Current labels:', currentLabels.join(', '));
|
|
||||||
console.log('Changed files:', changedFiles.length);
|
|
||||||
console.log('Total changes:', totalChanges);
|
|
||||||
if (isMegaPR) {
|
|
||||||
console.log('Mega-PR detected - applying limited labeling logic');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch API data
|
|
||||||
async function fetchApiData() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://data.esphome.io/components.json');
|
|
||||||
const componentsData = await response.json();
|
|
||||||
return {
|
|
||||||
targetPlatforms: componentsData.target_platforms || [],
|
|
||||||
platformComponents: componentsData.platform_components || []
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Failed to fetch components data from API:', error.message);
|
|
||||||
return { targetPlatforms: [], platformComponents: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Merge branch detection
|
|
||||||
async function detectMergeBranch() {
|
|
||||||
const labels = new Set();
|
|
||||||
const baseRef = context.payload.pull_request.base.ref;
|
|
||||||
|
|
||||||
if (baseRef === 'release') {
|
|
||||||
labels.add('merging-to-release');
|
|
||||||
} else if (baseRef === 'beta') {
|
|
||||||
labels.add('merging-to-beta');
|
|
||||||
} else if (baseRef !== 'dev') {
|
|
||||||
labels.add('chained-pr');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Component and platform labeling
|
|
||||||
async function detectComponentPlatforms(apiData) {
|
|
||||||
const labels = new Set();
|
|
||||||
const componentRegex = /^esphome\/components\/([^\/]+)\//;
|
|
||||||
const targetPlatformRegex = new RegExp(`^esphome\/components\/(${apiData.targetPlatforms.join('|')})/`);
|
|
||||||
|
|
||||||
for (const file of changedFiles) {
|
|
||||||
const componentMatch = file.match(componentRegex);
|
|
||||||
if (componentMatch) {
|
|
||||||
labels.add(`component: ${componentMatch[1]}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const platformMatch = file.match(targetPlatformRegex);
|
|
||||||
if (platformMatch) {
|
|
||||||
labels.add(`platform: ${platformMatch[1]}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: New component detection
|
|
||||||
async function detectNewComponents() {
|
|
||||||
const labels = new Set();
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
|
||||||
|
|
||||||
for (const file of addedFiles) {
|
|
||||||
const componentMatch = file.match(/^esphome\/components\/([^\/]+)\/__init__\.py$/);
|
|
||||||
if (componentMatch) {
|
|
||||||
try {
|
|
||||||
const content = fs.readFileSync(file, 'utf8');
|
|
||||||
if (content.includes('IS_TARGET_PLATFORM = True')) {
|
|
||||||
labels.add('new-target-platform');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to read content of ${file}:`, error.message);
|
|
||||||
}
|
|
||||||
labels.add('new-component');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: New platform detection
|
|
||||||
async function detectNewPlatforms(apiData) {
|
|
||||||
const labels = new Set();
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
|
||||||
|
|
||||||
for (const file of addedFiles) {
|
|
||||||
const platformFileMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\.py$/);
|
|
||||||
if (platformFileMatch) {
|
|
||||||
const [, component, platform] = platformFileMatch;
|
|
||||||
if (apiData.platformComponents.includes(platform)) {
|
|
||||||
labels.add('new-platform');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const platformDirMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/);
|
|
||||||
if (platformDirMatch) {
|
|
||||||
const [, component, platform] = platformDirMatch;
|
|
||||||
if (apiData.platformComponents.includes(platform)) {
|
|
||||||
labels.add('new-platform');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Core files detection
|
|
||||||
async function detectCoreChanges() {
|
|
||||||
const labels = new Set();
|
|
||||||
const coreFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('esphome/core/') ||
|
|
||||||
(file.startsWith('esphome/') && file.split('/').length === 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (coreFiles.length > 0) {
|
|
||||||
labels.add('core');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: PR size detection
|
|
||||||
async function detectPRSize() {
|
|
||||||
const labels = new Set();
|
|
||||||
|
|
||||||
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
|
||||||
labels.add('small-pr');
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
const testAdditions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
|
||||||
const testDeletions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
|
|
||||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
|
||||||
|
|
||||||
// Don't add too-big if mega-pr label is already present
|
|
||||||
if (nonTestChanges > TOO_BIG_THRESHOLD && !isMegaPR) {
|
|
||||||
labels.add('too-big');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Dashboard changes
|
|
||||||
async function detectDashboardChanges() {
|
|
||||||
const labels = new Set();
|
|
||||||
const dashboardFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('esphome/dashboard/') ||
|
|
||||||
file.startsWith('esphome/components/dashboard_import/')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dashboardFiles.length > 0) {
|
|
||||||
labels.add('dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: GitHub Actions changes
|
|
||||||
async function detectGitHubActionsChanges() {
|
|
||||||
const labels = new Set();
|
|
||||||
const githubActionsFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('.github/workflows/')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (githubActionsFiles.length > 0) {
|
|
||||||
labels.add('github-actions');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Code owner detection
|
|
||||||
async function detectCodeOwner() {
|
|
||||||
const labels = new Set();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data: codeownersFile } = await github.rest.repos.getContent({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
path: 'CODEOWNERS',
|
|
||||||
});
|
|
||||||
|
|
||||||
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
|
||||||
|
|
||||||
const codeownersLines = codeownersContent.split('\n')
|
|
||||||
.map(line => line.trim())
|
|
||||||
.filter(line => line && !line.startsWith('#'));
|
|
||||||
|
|
||||||
const codeownersRegexes = codeownersLines.map(line => {
|
|
||||||
const parts = line.split(/\s+/);
|
|
||||||
const pattern = parts[0];
|
|
||||||
const owners = parts.slice(1);
|
|
||||||
|
|
||||||
let regex;
|
|
||||||
if (pattern.endsWith('*')) {
|
|
||||||
const dir = pattern.slice(0, -1);
|
|
||||||
regex = new RegExp(`^${dir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
|
|
||||||
} else if (pattern.includes('*')) {
|
|
||||||
// First escape all regex special chars except *, then replace * with .*
|
|
||||||
const regexPattern = pattern
|
|
||||||
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
||||||
.replace(/\*/g, '.*');
|
|
||||||
regex = new RegExp(`^${regexPattern}$`);
|
|
||||||
} else {
|
|
||||||
regex = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { regex, owners };
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const file of changedFiles) {
|
|
||||||
for (const { regex, owners } of codeownersRegexes) {
|
|
||||||
if (regex.test(file) && owners.some(owner => owner === `@${prAuthor}`)) {
|
|
||||||
labels.add('by-code-owner');
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Failed to read or parse CODEOWNERS file:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Test detection
|
|
||||||
async function detectTests() {
|
|
||||||
const labels = new Set();
|
|
||||||
const testFiles = changedFiles.filter(file => file.startsWith('tests/'));
|
|
||||||
|
|
||||||
if (testFiles.length > 0) {
|
|
||||||
labels.add('has-tests');
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: PR Template Checkbox detection
|
|
||||||
async function detectPRTemplateCheckboxes() {
|
|
||||||
const labels = new Set();
|
|
||||||
const prBody = context.payload.pull_request.body || '';
|
|
||||||
|
|
||||||
console.log('Checking PR template checkboxes...');
|
|
||||||
|
|
||||||
// Check for checked checkboxes in the "Types of changes" section
|
|
||||||
const checkboxPatterns = [
|
|
||||||
{ pattern: /- \[x\] Bugfix \(non-breaking change which fixes an issue\)/i, label: 'bugfix' },
|
|
||||||
{ pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' },
|
|
||||||
{ pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' },
|
|
||||||
{ pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' },
|
|
||||||
{ pattern: /- \[x\] Code quality improvements to existing code or addition of tests/i, label: 'code-quality' }
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { pattern, label } of checkboxPatterns) {
|
|
||||||
if (pattern.test(prBody)) {
|
|
||||||
console.log(`Found checked checkbox for: ${label}`);
|
|
||||||
labels.add(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Requirements detection
|
|
||||||
async function detectRequirements(allLabels) {
|
|
||||||
const labels = new Set();
|
|
||||||
|
|
||||||
// Check for missing tests
|
|
||||||
if ((allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) && !allLabels.has('has-tests')) {
|
|
||||||
labels.add('needs-tests');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for missing docs
|
|
||||||
if (allLabels.has('new-component') || allLabels.has('new-platform') || allLabels.has('new-feature')) {
|
|
||||||
const prBody = context.payload.pull_request.body || '';
|
|
||||||
const hasDocsLink = DOCS_PR_PATTERNS.some(pattern => pattern.test(prBody));
|
|
||||||
|
|
||||||
if (!hasDocsLink) {
|
|
||||||
labels.add('needs-docs');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for missing CODEOWNERS
|
|
||||||
if (allLabels.has('new-component')) {
|
|
||||||
const codeownersModified = prFiles.some(file =>
|
|
||||||
file.filename === 'CODEOWNERS' &&
|
|
||||||
(file.status === 'modified' || file.status === 'added') &&
|
|
||||||
(file.additions || 0) > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!codeownersModified) {
|
|
||||||
labels.add('needs-codeowners');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate review messages
|
|
||||||
function generateReviewMessages(finalLabels, originalLabelCount) {
|
|
||||||
const messages = [];
|
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
|
||||||
|
|
||||||
// Too big message
|
|
||||||
if (finalLabels.includes('too-big')) {
|
|
||||||
const testAdditions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.additions || 0), 0);
|
|
||||||
const testDeletions = prFiles
|
|
||||||
.filter(file => file.filename.startsWith('tests/'))
|
|
||||||
.reduce((sum, file) => sum + (file.deletions || 0), 0);
|
|
||||||
const nonTestChanges = (totalAdditions - testAdditions) - (totalDeletions - testDeletions);
|
|
||||||
|
|
||||||
const tooManyLabels = originalLabelCount > MAX_LABELS;
|
|
||||||
const tooManyChanges = nonTestChanges > TOO_BIG_THRESHOLD;
|
|
||||||
|
|
||||||
let message = `${TOO_BIG_MARKER}\n### 📦 Pull Request Size\n\n`;
|
|
||||||
|
|
||||||
if (tooManyLabels && tooManyChanges) {
|
|
||||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests) and affects ${originalLabelCount} different components/areas.`;
|
|
||||||
} else if (tooManyLabels) {
|
|
||||||
message += `This PR affects ${originalLabelCount} different components/areas.`;
|
|
||||||
} else {
|
|
||||||
message += `This PR is too large with ${nonTestChanges} line changes (excluding tests).`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CODEOWNERS message
|
|
||||||
if (finalLabels.includes('needs-codeowners')) {
|
|
||||||
const message = `${CODEOWNERS_MARKER}\n### 👥 Code Ownership\n\n` +
|
|
||||||
`Hey there @${prAuthor},\n` +
|
|
||||||
`Thanks for submitting this pull request! Can you add yourself as a codeowner for this integration? ` +
|
|
||||||
`This way we can notify you if a bug report for this integration is reported.\n\n` +
|
|
||||||
`In \`__init__.py\` of the integration, please add:\n\n` +
|
|
||||||
`\`\`\`python\nCODEOWNERS = ["@${prAuthor}"]\n\`\`\`\n\n` +
|
|
||||||
`And run \`script/build_codeowners.py\``;
|
|
||||||
|
|
||||||
messages.push(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle reviews
|
|
||||||
async function handleReviews(finalLabels, originalLabelCount) {
|
|
||||||
const reviewMessages = generateReviewMessages(finalLabels, originalLabelCount);
|
|
||||||
const hasReviewableLabels = finalLabels.some(label =>
|
|
||||||
['too-big', 'needs-codeowners'].includes(label)
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: reviews } = await github.rest.pulls.listReviews({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number
|
|
||||||
});
|
|
||||||
|
|
||||||
const botReviews = reviews.filter(review =>
|
|
||||||
review.user.type === 'Bot' &&
|
|
||||||
review.state === 'CHANGES_REQUESTED' &&
|
|
||||||
review.body && review.body.includes(BOT_COMMENT_MARKER)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasReviewableLabels) {
|
|
||||||
const reviewBody = `${BOT_COMMENT_MARKER}\n\n${reviewMessages.join('\n\n---\n\n')}`;
|
|
||||||
|
|
||||||
if (botReviews.length > 0) {
|
|
||||||
// Update existing review
|
|
||||||
await github.rest.pulls.updateReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number,
|
|
||||||
review_id: botReviews[0].id,
|
|
||||||
body: reviewBody
|
|
||||||
});
|
|
||||||
console.log('Updated existing bot review');
|
|
||||||
} else {
|
|
||||||
// Create new review
|
|
||||||
await github.rest.pulls.createReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number,
|
|
||||||
body: reviewBody,
|
|
||||||
event: 'REQUEST_CHANGES'
|
|
||||||
});
|
|
||||||
console.log('Created new bot review');
|
|
||||||
}
|
|
||||||
} else if (botReviews.length > 0) {
|
|
||||||
// Dismiss existing reviews
|
|
||||||
for (const review of botReviews) {
|
|
||||||
try {
|
|
||||||
await github.rest.pulls.dismissReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number,
|
|
||||||
review_id: review.id,
|
|
||||||
message: 'Review dismissed: All requirements have been met'
|
|
||||||
});
|
|
||||||
console.log(`Dismissed bot review ${review.id}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to dismiss review ${review.id}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main execution
|
|
||||||
const apiData = await fetchApiData();
|
|
||||||
const baseRef = context.payload.pull_request.base.ref;
|
|
||||||
|
|
||||||
// Early exit for release and beta branches only
|
|
||||||
if (baseRef === 'release' || baseRef === 'beta') {
|
|
||||||
const branchLabels = await detectMergeBranch();
|
|
||||||
const finalLabels = Array.from(branchLabels);
|
|
||||||
|
|
||||||
console.log('Computed labels (merge branch only):', finalLabels.join(', '));
|
|
||||||
|
|
||||||
// Apply labels
|
|
||||||
if (finalLabels.length > 0) {
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr_number,
|
|
||||||
labels: finalLabels
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old managed labels
|
|
||||||
const labelsToRemove = managedLabels.filter(label => !finalLabels.includes(label));
|
|
||||||
for (const label of labelsToRemove) {
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr_number,
|
|
||||||
name: label
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to remove label ${label}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run all strategies
|
|
||||||
const [
|
|
||||||
branchLabels,
|
|
||||||
componentLabels,
|
|
||||||
newComponentLabels,
|
|
||||||
newPlatformLabels,
|
|
||||||
coreLabels,
|
|
||||||
sizeLabels,
|
|
||||||
dashboardLabels,
|
|
||||||
actionsLabels,
|
|
||||||
codeOwnerLabels,
|
|
||||||
testLabels,
|
|
||||||
checkboxLabels
|
|
||||||
] = await Promise.all([
|
|
||||||
detectMergeBranch(),
|
|
||||||
detectComponentPlatforms(apiData),
|
|
||||||
detectNewComponents(),
|
|
||||||
detectNewPlatforms(apiData),
|
|
||||||
detectCoreChanges(),
|
|
||||||
detectPRSize(),
|
|
||||||
detectDashboardChanges(),
|
|
||||||
detectGitHubActionsChanges(),
|
|
||||||
detectCodeOwner(),
|
|
||||||
detectTests(),
|
|
||||||
detectPRTemplateCheckboxes()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Combine all labels
|
|
||||||
const allLabels = new Set([
|
|
||||||
...branchLabels,
|
|
||||||
...componentLabels,
|
|
||||||
...newComponentLabels,
|
|
||||||
...newPlatformLabels,
|
|
||||||
...coreLabels,
|
|
||||||
...sizeLabels,
|
|
||||||
...dashboardLabels,
|
|
||||||
...actionsLabels,
|
|
||||||
...codeOwnerLabels,
|
|
||||||
...testLabels,
|
|
||||||
...checkboxLabels
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Detect requirements based on all other labels
|
|
||||||
const requirementLabels = await detectRequirements(allLabels);
|
|
||||||
for (const label of requirementLabels) {
|
|
||||||
allLabels.add(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalLabels = Array.from(allLabels);
|
|
||||||
|
|
||||||
// For mega-PRs, exclude component labels if there are too many
|
|
||||||
if (isMegaPR) {
|
|
||||||
const componentLabels = finalLabels.filter(label => label.startsWith('component: '));
|
|
||||||
if (componentLabels.length > COMPONENT_LABEL_THRESHOLD) {
|
|
||||||
finalLabels = finalLabels.filter(label => !label.startsWith('component: '));
|
|
||||||
console.log(`Mega-PR detected - excluding ${componentLabels.length} component labels (threshold: ${COMPONENT_LABEL_THRESHOLD})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle too many labels (only for non-mega PRs)
|
|
||||||
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
|
||||||
const originalLabelCount = finalLabels.length;
|
|
||||||
|
|
||||||
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
|
|
||||||
finalLabels = ['too-big'];
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Computed labels:', finalLabels.join(', '));
|
|
||||||
|
|
||||||
// Handle reviews
|
|
||||||
await handleReviews(finalLabels, originalLabelCount);
|
|
||||||
|
|
||||||
// Apply labels
|
|
||||||
if (finalLabels.length > 0) {
|
|
||||||
console.log(`Adding labels: ${finalLabels.join(', ')}`);
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr_number,
|
|
||||||
labels: finalLabels
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old managed labels
|
|
||||||
const labelsToRemove = managedLabels.filter(label => !finalLabels.includes(label));
|
|
||||||
for (const label of labelsToRemove) {
|
|
||||||
console.log(`Removing label: ${label}`);
|
|
||||||
try {
|
|
||||||
await github.rest.issues.removeLabel({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: pr_number,
|
|
||||||
name: label
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to remove label ${label}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
# yamllint disable-line rule:line-length
|
# yamllint disable-line rule:line-length
|
||||||
@@ -157,7 +157,7 @@ jobs:
|
|||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Save Python virtual environment cache
|
- name: Save Python virtual environment cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.restore-python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -193,7 +193,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Restore components graph cache
|
- name: Restore components graph cache
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
echo "component-test-batches=$(echo "$output" | jq -c '.component_test_batches')" >> $GITHUB_OUTPUT
|
||||||
- name: Save components graph cache
|
- name: Save components graph cache
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: .temp/components_graph.json
|
path: .temp/components_graph.json
|
||||||
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
key: components-graph-${{ hashFiles('esphome/components/**/*.py') }}
|
||||||
@@ -245,7 +245,7 @@ jobs:
|
|||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Restore Python virtual environment
|
- name: Restore Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||||
@@ -334,14 +334,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-${{ matrix.pio_cache_key }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -413,14 +413,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -502,14 +502,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: github.ref != 'refs/heads/dev'
|
if: github.ref != 'refs/heads/dev'
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
key: platformio-tidyesp32-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -735,7 +735,7 @@ jobs:
|
|||||||
- name: Restore cached memory analysis
|
- name: Restore cached memory analysis
|
||||||
id: cache-memory-analysis
|
id: cache-memory-analysis
|
||||||
if: steps.check-script.outputs.skip != 'true'
|
if: steps.check-script.outputs.skip != 'true'
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -759,7 +759,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true'
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
@@ -800,7 +800,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save memory analysis to cache
|
- name: Save memory analysis to cache
|
||||||
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
if: steps.check-script.outputs.skip != 'true' && steps.cache-memory-analysis.outputs.cache-hit != 'true' && steps.build.outcome == 'success'
|
||||||
uses: actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: memory-analysis-target.json
|
path: memory-analysis-target.json
|
||||||
key: ${{ steps.cache-key.outputs.cache-key }}
|
key: ${{ steps.cache-key.outputs.cache-key }}
|
||||||
@@ -847,7 +847,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
cache-key: ${{ needs.common.outputs.cache-key }}
|
cache-key: ${{ needs.common.outputs.cache-key }}
|
||||||
- name: Cache platformio
|
- name: Cache platformio
|
||||||
uses: actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
|
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio
|
path: ~/.platformio
|
||||||
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
key: platformio-memory-${{ fromJSON(needs.determine-jobs.outputs.memory_impact).platform }}-${{ hashFiles('platformio.ini') }}
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -102,12 +102,12 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -182,13 +182,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import logging
|
|||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor, voltage_sampler
|
from esphome.components import sensor, voltage_sampler
|
||||||
from esphome.components.esp32 import get_esp32_variant
|
from esphome.components.esp32 import get_esp32_variant, include_builtin_idf_component
|
||||||
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
|
from esphome.components.nrf52.const import AIN_TO_GPIO, EXTRA_ADC
|
||||||
from esphome.components.zephyr import (
|
from esphome.components.zephyr import (
|
||||||
zephyr_add_overlay,
|
zephyr_add_overlay,
|
||||||
@@ -118,6 +118,9 @@ async def to_code(config):
|
|||||||
cg.add(var.set_sampling_mode(config[CONF_SAMPLING_MODE]))
|
cg.add(var.set_sampling_mode(config[CONF_SAMPLING_MODE]))
|
||||||
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's ADC driver (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_adc")
|
||||||
|
|
||||||
if attenuation := config.get(CONF_ATTENUATION):
|
if attenuation := config.get(CONF_ATTENUATION):
|
||||||
if attenuation == "auto":
|
if attenuation == "auto":
|
||||||
cg.add(var.set_autorange(cg.global_ns.true))
|
cg.add(var.set_autorange(cg.global_ns.true))
|
||||||
|
|||||||
@@ -38,8 +38,10 @@ async def to_code(config):
|
|||||||
# https://github.com/ESP32Async/ESPAsyncTCP
|
# https://github.com/ESP32Async/ESPAsyncTCP
|
||||||
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
|
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")
|
||||||
elif CORE.is_rp2040:
|
elif CORE.is_rp2040:
|
||||||
# https://github.com/khoih-prog/AsyncTCP_RP2040W
|
# https://github.com/ayushsharma82/RPAsyncTCP
|
||||||
cg.add_library("khoih-prog/AsyncTCP_RP2040W", "1.2.0")
|
# RPAsyncTCP is a drop-in replacement for AsyncTCP_RP2040W with better
|
||||||
|
# ESPAsyncWebServer compatibility
|
||||||
|
cg.add_library("ayushsharma82/RPAsyncTCP", "1.3.2")
|
||||||
# Other platforms (host, etc) use socket-based implementation
|
# Other platforms (host, etc) use socket-based implementation
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
|
// Use ESPAsyncTCP library for ESP8266 (always Arduino)
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
#elif defined(USE_RP2040)
|
#elif defined(USE_RP2040)
|
||||||
// Use AsyncTCP_RP2040W library for RP2040
|
// Use RPAsyncTCP library for RP2040
|
||||||
#include <AsyncTCP_RP2040W.h>
|
#include <RPAsyncTCP.h>
|
||||||
#else
|
#else
|
||||||
// Use socket-based implementation for other platforms
|
// Use socket-based implementation for other platforms
|
||||||
#include "async_tcp_socket.h"
|
#include "async_tcp_socket.h"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import add_idf_component
|
from esphome.components.esp32 import add_idf_component, include_builtin_idf_component
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
@@ -166,6 +166,9 @@ def final_validate_audio_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_http_client")
|
||||||
|
|
||||||
add_idf_component(
|
add_idf_component(
|
||||||
name="esphome/esp-audio-libs",
|
name="esphome/esp-audio-libs",
|
||||||
ref="2.0.3",
|
ref="2.0.3",
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||||
|
LOG_ENTITY_DEVICE_CLASS(tag, prefix, *obj);
|
||||||
if (!obj->get_device_class_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BinarySensor::publish_state(bool new_state) {
|
void BinarySensor::publish_state(bool new_state) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
|
|||||||
void CalibrationNumber::setup() {
|
void CalibrationNumber::setup() {
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
if (this->restore_value_) {
|
if (this->restore_value_) {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<float>();
|
||||||
if (!this->pref_.load(&value)) {
|
if (!this->pref_.load(&value)) {
|
||||||
value = 0.0f;
|
value = 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,7 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||||
|
LOG_ENTITY_ICON(tag, prefix, *obj);
|
||||||
if (!obj->get_icon_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::press() {
|
void Button::press() {
|
||||||
|
|||||||
@@ -96,10 +96,16 @@ void CaptivePortal::start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
|
||||||
if (req->url() == ESPHOME_F("/config.json")) {
|
#ifdef USE_ESP32
|
||||||
|
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
|
||||||
|
StringRef url = req->url_to(url_buf);
|
||||||
|
#else
|
||||||
|
const auto &url = req->url();
|
||||||
|
#endif
|
||||||
|
if (url == ESPHOME_F("/config.json")) {
|
||||||
this->handle_config(req);
|
this->handle_config(req);
|
||||||
return;
|
return;
|
||||||
} else if (req->url() == ESPHOME_F("/wifisave")) {
|
} else if (url == ESPHOME_F("/wifisave")) {
|
||||||
this->handle_wifisave(req);
|
this->handle_wifisave(req);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,8 +360,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
|
|||||||
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
|
||||||
|
|
||||||
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
optional<ClimateDeviceRestoreState> Climate::restore_state_() {
|
||||||
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^
|
this->rtc_ = this->make_entity_preference<ClimateDeviceRestoreState>(RESTORE_STATE_VERSION);
|
||||||
RESTORE_STATE_VERSION);
|
|
||||||
ClimateDeviceRestoreState recovered{};
|
ClimateDeviceRestoreState recovered{};
|
||||||
if (!this->rtc_.load(&recovered))
|
if (!this->rtc_.load(&recovered))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ void Cover::publish_state(bool save) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
optional<CoverRestoreState> Cover::restore_state_() {
|
optional<CoverRestoreState> Cover::restore_state_() {
|
||||||
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash());
|
this->rtc_ = this->make_entity_preference<CoverRestoreState>();
|
||||||
CoverRestoreState recovered{};
|
CoverRestoreState recovered{};
|
||||||
if (!this->rtc_.load(&recovered))
|
if (!this->rtc_.load(&recovered))
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ const extern float COVER_CLOSED;
|
|||||||
if (traits_.get_is_assumed_state()) { \
|
if (traits_.get_is_assumed_state()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||||
} \
|
} \
|
||||||
if (!(obj)->get_device_class_ref().empty()) { \
|
LOG_ENTITY_DEVICE_CLASS(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Cover;
|
class Cover;
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ namespace esphome::datetime {
|
|||||||
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
#define LOG_DATETIME_DATE(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateCall;
|
class DateCall;
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ namespace esphome::datetime {
|
|||||||
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateTimeCall;
|
class DateTimeCall;
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ namespace esphome::datetime {
|
|||||||
#define LOG_DATETIME_TIME(prefix, type, obj) \
|
#define LOG_DATETIME_TIME(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeCall;
|
class TimeCall;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from esphome.const import (
|
|||||||
CONF_UPDATE_INTERVAL,
|
CONF_UPDATE_INTERVAL,
|
||||||
SCHEDULER_DONT_RUN,
|
SCHEDULER_DONT_RUN,
|
||||||
)
|
)
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
@@ -222,3 +222,8 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_global(display_ns.using)
|
cg.add_global(display_ns.using)
|
||||||
cg.add_define("USE_DISPLAY")
|
cg.add_define("USE_DISPLAY")
|
||||||
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's LCD driver (excluded by default to save compile time)
|
||||||
|
from esphome.components.esp32 import include_builtin_idf_component
|
||||||
|
|
||||||
|
include_builtin_idf_component("esp_lcd")
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
|
|||||||
uint32_t seconds = 0;
|
uint32_t seconds = 0;
|
||||||
|
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<uint32_t>();
|
||||||
this->pref_.load(&seconds);
|
this->pref_.load(&seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.components.audio_dac import AudioDac
|
from esphome.components.audio_dac import AudioDac
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_AUDIO_DAC, CONF_BITS_PER_SAMPLE, CONF_ID
|
||||||
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
CODEOWNERS = ["@kbx81"]
|
CODEOWNERS = ["@kbx81"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@@ -21,6 +22,29 @@ CONFIG_SCHEMA = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _final_validate(config):
|
||||||
|
full_config = fv.full_config.get()
|
||||||
|
|
||||||
|
# Check all speaker configurations for ones that reference this es8156
|
||||||
|
speaker_configs = full_config.get("speaker", [])
|
||||||
|
for speaker_config in speaker_configs:
|
||||||
|
audio_dac_id = speaker_config.get(CONF_AUDIO_DAC)
|
||||||
|
if (
|
||||||
|
audio_dac_id is not None
|
||||||
|
and audio_dac_id == config[CONF_ID]
|
||||||
|
and (bits_per_sample := speaker_config.get(CONF_BITS_PER_SAMPLE))
|
||||||
|
is not None
|
||||||
|
and bits_per_sample > 24
|
||||||
|
):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"ES8156 does not support more than 24 bits per sample. "
|
||||||
|
f"The speaker referencing this audio_dac has bits_per_sample set to {bits_per_sample}."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -17,24 +17,61 @@ static const char *const TAG = "es8156";
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ES8156::setup() {
|
void ES8156::setup() {
|
||||||
|
// REG02 MODE CONFIG 1: Enable software mode for I2C control of volume/mute
|
||||||
|
// Bit 2: SOFT_MODE_SEL=1 (software mode enabled)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG02_SCLK_MODE, 0x04));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG02_SCLK_MODE, 0x04));
|
||||||
|
|
||||||
|
// Analog system configuration (active-low power down bits, active-high enables)
|
||||||
|
// REG20 ANALOG SYSTEM: Configure analog signal path
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG20_ANALOG_SYS1, 0x2A));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG20_ANALOG_SYS1, 0x2A));
|
||||||
|
|
||||||
|
// REG21 ANALOG SYSTEM: VSEL=0x1C (bias level ~120%), normal VREF ramp speed
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG21_ANALOG_SYS2, 0x3C));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG21_ANALOG_SYS2, 0x3C));
|
||||||
|
|
||||||
|
// REG22 ANALOG SYSTEM: Line out mode (HPSW=0), OUT_MUTE=0 (not muted)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG22_ANALOG_SYS3, 0x00));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG22_ANALOG_SYS3, 0x00));
|
||||||
|
|
||||||
|
// REG24 ANALOG SYSTEM: Low power mode for VREFBUF, HPCOM, DACVRP; DAC normal power
|
||||||
|
// Bits 2:0 = 0x07: LPVREFBUF=1, LPHPCOM=1, LPDACVRP=1, LPDAC=0
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG24_ANALOG_LP, 0x07));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG24_ANALOG_LP, 0x07));
|
||||||
|
|
||||||
|
// REG23 ANALOG SYSTEM: Lowest bias (IBIAS_SW=0), VMIDLVL=VDDA/2, normal impedance
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG23_ANALOG_SYS4, 0x00));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG23_ANALOG_SYS4, 0x00));
|
||||||
|
|
||||||
|
// Timing and interface configuration
|
||||||
|
// REG0A/0B TIME CONTROL: Fast state machine transitions
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0A_TIME_CONTROL1, 0x01));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0A_TIME_CONTROL1, 0x01));
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0B_TIME_CONTROL2, 0x01));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0B_TIME_CONTROL2, 0x01));
|
||||||
|
|
||||||
|
// REG11 SDP INTERFACE CONFIG: Default I2S format (24-bit, I2S mode)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG11_DAC_SDP, 0x00));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG11_DAC_SDP, 0x00));
|
||||||
|
|
||||||
|
// REG19 EQ CONTROL 1: EQ disabled (EQ_ON=0), EQ_BAND_NUM=2
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG19_EQ_CONTROL1, 0x20));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG19_EQ_CONTROL1, 0x20));
|
||||||
|
|
||||||
|
// REG0D P2S CONTROL: Parallel-to-serial converter settings
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0D_P2S_CONTROL, 0x14));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG0D_P2S_CONTROL, 0x14));
|
||||||
|
|
||||||
|
// REG09 MISC CONTROL 2: Default settings
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG09_MISC_CONTROL2, 0x00));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG09_MISC_CONTROL2, 0x00));
|
||||||
|
|
||||||
|
// REG18 MISC CONTROL 3: Stereo channel routing, no inversion
|
||||||
|
// Bits 5:4 CHN_CROSS: 0=L→L/R→R, 1=L to both, 2=R to both, 3=swap L/R
|
||||||
|
// Bits 3:2: LCH_INV/RCH_INV channel inversion
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG18_MISC_CONTROL3, 0x00));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG18_MISC_CONTROL3, 0x00));
|
||||||
|
|
||||||
|
// REG08 CLOCK OFF: Enable all internal clocks (0x3F = all clock gates open)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG08_CLOCK_ON_OFF, 0x3F));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG08_CLOCK_ON_OFF, 0x3F));
|
||||||
|
|
||||||
|
// REG00 RESET CONTROL: Reset sequence
|
||||||
|
// First: RST_DIG=1 (assert digital reset)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x02));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x02));
|
||||||
|
// Then: CSM_ON=1 (enable chip state machine), RST_DIG=1
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x03));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG00_RESET, 0x03));
|
||||||
|
|
||||||
|
// REG25 ANALOG SYSTEM: Power up analog blocks
|
||||||
|
// VMIDSEL=2 (normal VMID operation), PDN_ANA=0, ENREFR=0, ENHPCOM=0
|
||||||
|
// PDN_DACVREFGEN=0, PDN_VREFBUF=0, PDN_DAC=0 (all enabled)
|
||||||
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG25_ANALOG_SYS5, 0x20));
|
ES8156_ERROR_FAILED(this->write_byte(ES8156_REG25_ANALOG_SYS5, 0x20));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,10 @@ from .const import ( # noqa
|
|||||||
KEY_BOARD,
|
KEY_BOARD,
|
||||||
KEY_COMPONENTS,
|
KEY_COMPONENTS,
|
||||||
KEY_ESP32,
|
KEY_ESP32,
|
||||||
|
KEY_EXCLUDE_COMPONENTS,
|
||||||
KEY_EXTRA_BUILD_FILES,
|
KEY_EXTRA_BUILD_FILES,
|
||||||
KEY_FLASH_SIZE,
|
KEY_FLASH_SIZE,
|
||||||
|
KEY_FULL_CERT_BUNDLE,
|
||||||
KEY_PATH,
|
KEY_PATH,
|
||||||
KEY_REF,
|
KEY_REF,
|
||||||
KEY_REPO,
|
KEY_REPO,
|
||||||
@@ -85,6 +87,7 @@ IS_TARGET_PLATFORM = True
|
|||||||
CONF_ASSERTION_LEVEL = "assertion_level"
|
CONF_ASSERTION_LEVEL = "assertion_level"
|
||||||
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
||||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||||
|
CONF_INCLUDE_BUILTIN_IDF_COMPONENTS = "include_builtin_idf_components"
|
||||||
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||||
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
|
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
|
||||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||||
@@ -113,6 +116,36 @@ COMPILER_OPTIMIZATIONS = {
|
|||||||
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
|
"SIZE": "CONFIG_COMPILER_OPTIMIZATION_SIZE",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ESP-IDF components excluded by default to reduce compile time.
|
||||||
|
# Components can be re-enabled by calling include_builtin_idf_component() in to_code().
|
||||||
|
#
|
||||||
|
# Cannot be excluded (dependencies of required components):
|
||||||
|
# - "console": espressif/mdns unconditionally depends on it
|
||||||
|
# - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain
|
||||||
|
DEFAULT_EXCLUDED_IDF_COMPONENTS = (
|
||||||
|
"cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing
|
||||||
|
"esp_adc", # ADC driver - only needed by adc component
|
||||||
|
"esp_driver_i2s", # I2S driver - only needed by i2s_audio component
|
||||||
|
"esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus
|
||||||
|
"esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch
|
||||||
|
"esp_eth", # Ethernet driver - only needed by ethernet component
|
||||||
|
"esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality
|
||||||
|
"esp_http_client", # HTTP client - only needed by http_request component
|
||||||
|
"esp_https_ota", # ESP-IDF HTTPS OTA - ESPHome has its own OTA implementation
|
||||||
|
"esp_https_server", # HTTPS server - ESPHome has its own web server
|
||||||
|
"esp_lcd", # LCD controller drivers - only needed by display component
|
||||||
|
"esp_local_ctrl", # Local control over HTTPS/BLE - ESPHome has native API
|
||||||
|
"espcoredump", # Core dump support - ESPHome has its own debug component
|
||||||
|
"fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage
|
||||||
|
"mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation
|
||||||
|
"perfmon", # Xtensa performance monitor - ESPHome has its own debug component
|
||||||
|
"protocomm", # Protocol communication for provisioning - unused by ESPHome
|
||||||
|
"spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only)
|
||||||
|
"unity", # Unit testing framework - ESPHome doesn't use IDF's testing
|
||||||
|
"wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused
|
||||||
|
"wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation
|
||||||
|
)
|
||||||
|
|
||||||
# ESP32 (original) chip revision options
|
# ESP32 (original) chip revision options
|
||||||
# Setting minimum revision to 3.0 or higher:
|
# Setting minimum revision to 3.0 or higher:
|
||||||
# - Reduces flash size by excluding workaround code for older chip bugs
|
# - Reduces flash size by excluding workaround code for older chip bugs
|
||||||
@@ -202,6 +235,9 @@ def set_core_data(config):
|
|||||||
)
|
)
|
||||||
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
CORE.data[KEY_ESP32][KEY_SDKCONFIG_OPTIONS] = {}
|
||||||
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
CORE.data[KEY_ESP32][KEY_COMPONENTS] = {}
|
||||||
|
# Initialize with default exclusions - components can call include_builtin_idf_component()
|
||||||
|
# to re-enable any they need
|
||||||
|
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS] = set(DEFAULT_EXCLUDED_IDF_COMPONENTS)
|
||||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version.parse(
|
||||||
config[CONF_FRAMEWORK][CONF_VERSION]
|
config[CONF_FRAMEWORK][CONF_VERSION]
|
||||||
)
|
)
|
||||||
@@ -327,6 +363,28 @@ def add_idf_component(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def exclude_builtin_idf_component(name: str) -> None:
|
||||||
|
"""Exclude an ESP-IDF component from the build.
|
||||||
|
|
||||||
|
This reduces compile time by skipping components that are not needed.
|
||||||
|
The component will be passed to ESP-IDF's EXCLUDE_COMPONENTS cmake variable.
|
||||||
|
|
||||||
|
Note: Components that are dependencies of other required components
|
||||||
|
cannot be excluded - ESP-IDF will still build them.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS].add(name)
|
||||||
|
|
||||||
|
|
||||||
|
def include_builtin_idf_component(name: str) -> None:
|
||||||
|
"""Remove an ESP-IDF component from the exclusion list.
|
||||||
|
|
||||||
|
Call this from components that need an ESP-IDF component that is
|
||||||
|
excluded by default in DEFAULT_EXCLUDED_IDF_COMPONENTS. This ensures the
|
||||||
|
component will be built when needed.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_EXCLUDE_COMPONENTS].discard(name)
|
||||||
|
|
||||||
|
|
||||||
def add_extra_script(stage: str, filename: str, path: Path):
|
def add_extra_script(stage: str, filename: str, path: Path):
|
||||||
"""Add an extra script to the project."""
|
"""Add an extra script to the project."""
|
||||||
key = f"{stage}:{filename}"
|
key = f"{stage}:{filename}"
|
||||||
@@ -670,11 +728,26 @@ CONF_FREERTOS_IN_IRAM = "freertos_in_iram"
|
|||||||
CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram"
|
CONF_RINGBUF_IN_IRAM = "ringbuf_in_iram"
|
||||||
CONF_HEAP_IN_IRAM = "heap_in_iram"
|
CONF_HEAP_IN_IRAM = "heap_in_iram"
|
||||||
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
|
||||||
|
CONF_USE_FULL_CERTIFICATE_BUNDLE = "use_full_certificate_bundle"
|
||||||
|
CONF_DISABLE_DEBUG_STUBS = "disable_debug_stubs"
|
||||||
|
CONF_DISABLE_OCD_AWARE = "disable_ocd_aware"
|
||||||
|
CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY = "disable_usb_serial_jtag_secondary"
|
||||||
|
CONF_DISABLE_DEV_NULL_VFS = "disable_dev_null_vfs"
|
||||||
|
CONF_DISABLE_MBEDTLS_PEER_CERT = "disable_mbedtls_peer_cert"
|
||||||
|
CONF_DISABLE_MBEDTLS_PKCS7 = "disable_mbedtls_pkcs7"
|
||||||
|
CONF_DISABLE_REGI2C_IN_IRAM = "disable_regi2c_in_iram"
|
||||||
|
CONF_DISABLE_FATFS = "disable_fatfs"
|
||||||
|
|
||||||
# VFS requirement tracking
|
# VFS requirement tracking
|
||||||
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
|
# Components that need VFS features can call require_vfs_select() or require_vfs_dir()
|
||||||
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
|
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
|
||||||
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
|
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
|
||||||
|
# Feature requirement tracking - components can call require_* functions to re-enable
|
||||||
|
# These are stored in CORE.data[KEY_ESP32] dict
|
||||||
|
KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED = "usb_serial_jtag_secondary_required"
|
||||||
|
KEY_MBEDTLS_PEER_CERT_REQUIRED = "mbedtls_peer_cert_required"
|
||||||
|
KEY_MBEDTLS_PKCS7_REQUIRED = "mbedtls_pkcs7_required"
|
||||||
|
KEY_FATFS_REQUIRED = "fatfs_required"
|
||||||
|
|
||||||
|
|
||||||
def require_vfs_select() -> None:
|
def require_vfs_select() -> None:
|
||||||
@@ -695,6 +768,55 @@ def require_vfs_dir() -> None:
|
|||||||
CORE.data[KEY_VFS_DIR_REQUIRED] = True
|
CORE.data[KEY_VFS_DIR_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_full_certificate_bundle() -> None:
|
||||||
|
"""Request the full certificate bundle instead of the common-CAs-only bundle.
|
||||||
|
|
||||||
|
By default, ESPHome uses CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN which
|
||||||
|
includes only CAs with >1% market share (~51 KB smaller than full bundle).
|
||||||
|
This covers ~99% of websites including Let's Encrypt, DigiCert, Google, Amazon.
|
||||||
|
|
||||||
|
Call this from components that need to connect to services using uncommon CAs.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_FULL_CERT_BUNDLE] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_usb_serial_jtag_secondary() -> None:
|
||||||
|
"""Mark that USB Serial/JTAG secondary console is required by a component.
|
||||||
|
|
||||||
|
Call this from components (e.g., logger) that need USB Serial/JTAG console output.
|
||||||
|
This prevents CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG from being disabled.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_mbedtls_peer_cert() -> None:
|
||||||
|
"""Mark that mbedTLS peer certificate retention is required by a component.
|
||||||
|
|
||||||
|
Call this from components that need access to the peer certificate after
|
||||||
|
the TLS handshake is complete. This prevents CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
|
||||||
|
from being disabled.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_MBEDTLS_PEER_CERT_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_mbedtls_pkcs7() -> None:
|
||||||
|
"""Mark that mbedTLS PKCS#7 support is required by a component.
|
||||||
|
|
||||||
|
Call this from components that need PKCS#7 certificate validation.
|
||||||
|
This prevents CONFIG_MBEDTLS_PKCS7_C from being disabled.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_MBEDTLS_PKCS7_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
|
def require_fatfs() -> None:
|
||||||
|
"""Mark that FATFS support is required by a component.
|
||||||
|
|
||||||
|
Call this from components that use FATFS (e.g., SD card, storage components).
|
||||||
|
This prevents FATFS from being disabled when disable_fatfs is set.
|
||||||
|
"""
|
||||||
|
CORE.data[KEY_ESP32][KEY_FATFS_REQUIRED] = True
|
||||||
|
|
||||||
|
|
||||||
def _parse_idf_component(value: str) -> ConfigType:
|
def _parse_idf_component(value: str) -> ConfigType:
|
||||||
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
|
||||||
# Match operator followed by version-like string (digit or *)
|
# Match operator followed by version-like string (digit or *)
|
||||||
@@ -776,6 +898,22 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
|||||||
min=8192, max=32768
|
min=8192, max=32768
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
|
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_USE_FULL_CERTIFICATE_BUNDLE, default=False
|
||||||
|
): cv.boolean,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_INCLUDE_BUILTIN_IDF_COMPONENTS, default=[]
|
||||||
|
): cv.ensure_list(cv.string_strict),
|
||||||
|
cv.Optional(CONF_DISABLE_DEBUG_STUBS, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_OCD_AWARE, default=True): cv.boolean,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY, default=True
|
||||||
|
): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_DEV_NULL_VFS, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_MBEDTLS_PEER_CERT, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_MBEDTLS_PKCS7, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_REGI2C_IN_IRAM, default=True): cv.boolean,
|
||||||
|
cv.Optional(CONF_DISABLE_FATFS, default=True): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||||
@@ -965,6 +1103,19 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
|
|||||||
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_SOCKETS", max_sockets)
|
add_idf_sdkconfig_option("CONFIG_LWIP_MAX_SOCKETS", max_sockets)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(CoroPriority.FINAL)
|
||||||
|
async def _write_exclude_components() -> None:
|
||||||
|
"""Write EXCLUDE_COMPONENTS cmake arg after all components have registered exclusions."""
|
||||||
|
if KEY_ESP32 not in CORE.data:
|
||||||
|
return
|
||||||
|
excluded = CORE.data[KEY_ESP32].get(KEY_EXCLUDE_COMPONENTS)
|
||||||
|
if excluded:
|
||||||
|
exclude_list = ";".join(sorted(excluded))
|
||||||
|
cg.add_platformio_option(
|
||||||
|
"board_build.cmake_extra_args", f"-DEXCLUDE_COMPONENTS={exclude_list}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(CoroPriority.FINAL)
|
@coroutine_with_priority(CoroPriority.FINAL)
|
||||||
async def _add_yaml_idf_components(components: list[ConfigType]):
|
async def _add_yaml_idf_components(components: list[ConfigType]):
|
||||||
"""Add IDF components from YAML config with final priority to override code-added components."""
|
"""Add IDF components from YAML config with final priority to override code-added components."""
|
||||||
@@ -1048,6 +1199,19 @@ async def to_code(config):
|
|||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||||
if use_platformio:
|
if use_platformio:
|
||||||
cg.add_platformio_option("framework", "espidf")
|
cg.add_platformio_option("framework", "espidf")
|
||||||
|
|
||||||
|
# Wrap std::__throw_* functions to abort immediately, eliminating ~3KB of
|
||||||
|
# exception class overhead. See throw_stubs.cpp for implementation.
|
||||||
|
# ESP-IDF already compiles with -fno-exceptions, so this code was dead anyway.
|
||||||
|
for mangled in [
|
||||||
|
"_ZSt20__throw_length_errorPKc",
|
||||||
|
"_ZSt19__throw_logic_errorPKc",
|
||||||
|
"_ZSt20__throw_out_of_rangePKc",
|
||||||
|
"_ZSt24__throw_out_of_range_fmtPKcz",
|
||||||
|
"_ZSt17__throw_bad_allocv",
|
||||||
|
"_ZSt25__throw_bad_function_callv",
|
||||||
|
]:
|
||||||
|
cg.add_build_flag(f"-Wl,--wrap={mangled}")
|
||||||
else:
|
else:
|
||||||
cg.add_build_flag("-DUSE_ARDUINO")
|
cg.add_build_flag("-DUSE_ARDUINO")
|
||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||||
@@ -1080,6 +1244,18 @@ async def to_code(config):
|
|||||||
|
|
||||||
cg.add_build_flag("-Wno-nonnull-compare")
|
cg.add_build_flag("-Wno-nonnull-compare")
|
||||||
|
|
||||||
|
# Use CMN (common CAs) bundle by default to save ~51KB flash
|
||||||
|
# CMN covers CAs with >1% market share (~99% of websites)
|
||||||
|
# Components needing uncommon CAs can call require_full_certificate_bundle()
|
||||||
|
use_full_bundle = conf[CONF_ADVANCED].get(
|
||||||
|
CONF_USE_FULL_CERTIFICATE_BUNDLE, False
|
||||||
|
) or CORE.data[KEY_ESP32].get(KEY_FULL_CERT_BUNDLE, False)
|
||||||
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL", use_full_bundle
|
||||||
|
)
|
||||||
|
if not use_full_bundle:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN", True)
|
||||||
|
|
||||||
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
||||||
@@ -1153,6 +1329,11 @@ async def to_code(config):
|
|||||||
|
|
||||||
# Apply LWIP optimization settings
|
# Apply LWIP optimization settings
|
||||||
advanced = conf[CONF_ADVANCED]
|
advanced = conf[CONF_ADVANCED]
|
||||||
|
|
||||||
|
# Re-include any IDF components the user explicitly requested
|
||||||
|
for component_name in advanced.get(CONF_INCLUDE_BUILTIN_IDF_COMPONENTS, []):
|
||||||
|
include_builtin_idf_component(component_name)
|
||||||
|
|
||||||
# DHCP server: only disable if explicitly set to false
|
# DHCP server: only disable if explicitly set to false
|
||||||
# WiFi component handles its own optimization when AP mode is not used
|
# WiFi component handles its own optimization when AP mode is not used
|
||||||
# When using Arduino with Ethernet, DHCP server functions must be available
|
# When using Arduino with Ethernet, DHCP server functions must be available
|
||||||
@@ -1274,6 +1455,61 @@ async def to_code(config):
|
|||||||
|
|
||||||
add_idf_sdkconfig_option(f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True)
|
add_idf_sdkconfig_option(f"CONFIG_LOG_DEFAULT_LEVEL_{conf[CONF_LOG_LEVEL]}", True)
|
||||||
|
|
||||||
|
# Disable OpenOCD debug stubs to save code size
|
||||||
|
# These are used for on-chip debugging with OpenOCD/JTAG, rarely needed for ESPHome
|
||||||
|
if advanced[CONF_DISABLE_DEBUG_STUBS]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_DEBUG_STUBS_ENABLE", False)
|
||||||
|
|
||||||
|
# Disable OCD-aware exception handlers
|
||||||
|
# When enabled, the panic handler detects JTAG debugger and halts instead of resetting
|
||||||
|
# Most ESPHome users don't use JTAG debugging
|
||||||
|
if advanced[CONF_DISABLE_OCD_AWARE]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_DEBUG_OCDAWARE", False)
|
||||||
|
|
||||||
|
# Disable USB Serial/JTAG secondary console
|
||||||
|
# Components like logger can call require_usb_serial_jtag_secondary() to re-enable
|
||||||
|
if CORE.data[KEY_ESP32].get(KEY_USB_SERIAL_JTAG_SECONDARY_REQUIRED, False):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG", True)
|
||||||
|
elif advanced[CONF_DISABLE_USB_SERIAL_JTAG_SECONDARY]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_SECONDARY_NONE", True)
|
||||||
|
|
||||||
|
# Disable /dev/null VFS initialization
|
||||||
|
# ESPHome doesn't typically need /dev/null
|
||||||
|
if advanced[CONF_DISABLE_DEV_NULL_VFS]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_VFS_INITIALIZE_DEV_NULL", False)
|
||||||
|
|
||||||
|
# Disable keeping peer certificate after TLS handshake
|
||||||
|
# Saves ~4KB heap per connection, but prevents certificate inspection after handshake
|
||||||
|
# Components that need it can call require_mbedtls_peer_cert()
|
||||||
|
if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PEER_CERT_REQUIRED, False):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", True)
|
||||||
|
elif advanced[CONF_DISABLE_MBEDTLS_PEER_CERT]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE", False)
|
||||||
|
|
||||||
|
# Disable PKCS#7 support in mbedTLS
|
||||||
|
# Only needed for specific certificate validation scenarios
|
||||||
|
# Components that need it can call require_mbedtls_pkcs7()
|
||||||
|
if CORE.data[KEY_ESP32].get(KEY_MBEDTLS_PKCS7_REQUIRED, False):
|
||||||
|
# Component called require_mbedtls_pkcs7() - enable regardless of user setting
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", True)
|
||||||
|
elif advanced[CONF_DISABLE_MBEDTLS_PKCS7]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PKCS7_C", False)
|
||||||
|
|
||||||
|
# Disable regi2c control functions in IRAM
|
||||||
|
# Only needed if using analog peripherals (ADC, DAC, etc.) from ISRs while cache is disabled
|
||||||
|
if advanced[CONF_DISABLE_REGI2C_IN_IRAM]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM", False)
|
||||||
|
|
||||||
|
# Disable FATFS support
|
||||||
|
# Components that need FATFS (SD card, etc.) can call require_fatfs()
|
||||||
|
if CORE.data[KEY_ESP32].get(KEY_FATFS_REQUIRED, False):
|
||||||
|
# Component called require_fatfs() - enable regardless of user setting
|
||||||
|
add_idf_sdkconfig_option("CONFIG_FATFS_LFN_NONE", False)
|
||||||
|
add_idf_sdkconfig_option("CONFIG_FATFS_VOLUME_COUNT", 2)
|
||||||
|
elif advanced[CONF_DISABLE_FATFS]:
|
||||||
|
add_idf_sdkconfig_option("CONFIG_FATFS_LFN_NONE", True)
|
||||||
|
add_idf_sdkconfig_option("CONFIG_FATFS_VOLUME_COUNT", 0)
|
||||||
|
|
||||||
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
for name, value in conf[CONF_SDKCONFIG_OPTIONS].items():
|
||||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||||
|
|
||||||
@@ -1282,6 +1518,11 @@ async def to_code(config):
|
|||||||
if conf[CONF_COMPONENTS]:
|
if conf[CONF_COMPONENTS]:
|
||||||
CORE.add_job(_add_yaml_idf_components, conf[CONF_COMPONENTS])
|
CORE.add_job(_add_yaml_idf_components, conf[CONF_COMPONENTS])
|
||||||
|
|
||||||
|
# Write EXCLUDE_COMPONENTS at FINAL priority after all components have had
|
||||||
|
# a chance to call include_builtin_idf_component() to re-enable components they need.
|
||||||
|
# Default exclusions are added in set_core_data() during config validation.
|
||||||
|
CORE.add_job(_write_exclude_components)
|
||||||
|
|
||||||
|
|
||||||
APP_PARTITION_SIZES = {
|
APP_PARTITION_SIZES = {
|
||||||
"2MB": 0x0C0000, # 768 KB
|
"2MB": 0x0C0000, # 768 KB
|
||||||
|
|||||||
@@ -175,6 +175,32 @@ ESP32_BOARD_PINS = {
|
|||||||
"LED": 13,
|
"LED": 13,
|
||||||
"LED_BUILTIN": 13,
|
"LED_BUILTIN": 13,
|
||||||
},
|
},
|
||||||
|
"adafruit_feather_esp32s3_reversetft": {
|
||||||
|
"BUTTON": 0,
|
||||||
|
"A0": 18,
|
||||||
|
"A1": 17,
|
||||||
|
"A2": 16,
|
||||||
|
"A3": 15,
|
||||||
|
"A4": 14,
|
||||||
|
"A5": 8,
|
||||||
|
"SCK": 36,
|
||||||
|
"MOSI": 35,
|
||||||
|
"MISO": 37,
|
||||||
|
"RX": 38,
|
||||||
|
"TX": 39,
|
||||||
|
"SCL": 4,
|
||||||
|
"SDA": 3,
|
||||||
|
"NEOPIXEL": 33,
|
||||||
|
"PIN_NEOPIXEL": 33,
|
||||||
|
"NEOPIXEL_POWER": 21,
|
||||||
|
"TFT_I2C_POWER": 7,
|
||||||
|
"TFT_CS": 42,
|
||||||
|
"TFT_DC": 40,
|
||||||
|
"TFT_RESET": 41,
|
||||||
|
"TFT_BACKLIGHT": 45,
|
||||||
|
"LED": 13,
|
||||||
|
"LED_BUILTIN": 13,
|
||||||
|
},
|
||||||
"adafruit_feather_esp32s3_tft": {
|
"adafruit_feather_esp32s3_tft": {
|
||||||
"BUTTON": 0,
|
"BUTTON": 0,
|
||||||
"A0": 18,
|
"A0": 18,
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ KEY_FLASH_SIZE = "flash_size"
|
|||||||
KEY_VARIANT = "variant"
|
KEY_VARIANT = "variant"
|
||||||
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
KEY_SDKCONFIG_OPTIONS = "sdkconfig_options"
|
||||||
KEY_COMPONENTS = "components"
|
KEY_COMPONENTS = "components"
|
||||||
|
KEY_EXCLUDE_COMPONENTS = "exclude_components"
|
||||||
KEY_REPO = "repo"
|
KEY_REPO = "repo"
|
||||||
KEY_REF = "ref"
|
KEY_REF = "ref"
|
||||||
KEY_REFRESH = "refresh"
|
KEY_REFRESH = "refresh"
|
||||||
KEY_PATH = "path"
|
KEY_PATH = "path"
|
||||||
KEY_SUBMODULES = "submodules"
|
KEY_SUBMODULES = "submodules"
|
||||||
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
KEY_EXTRA_BUILD_FILES = "extra_build_files"
|
||||||
|
KEY_FULL_CERT_BUNDLE = "full_cert_bundle"
|
||||||
|
|
||||||
VARIANT_ESP32 = "ESP32"
|
VARIANT_ESP32 = "ESP32"
|
||||||
VARIANT_ESP32C2 = "ESP32C2"
|
VARIANT_ESP32C2 = "ESP32C2"
|
||||||
|
|||||||
57
esphome/components/esp32/throw_stubs.cpp
Normal file
57
esphome/components/esp32/throw_stubs.cpp
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Linker wrap stubs for std::__throw_* functions.
|
||||||
|
*
|
||||||
|
* ESP-IDF compiles with -fno-exceptions, so C++ exceptions always abort.
|
||||||
|
* However, ESP-IDF only wraps low-level functions (__cxa_throw, etc.),
|
||||||
|
* not the std::__throw_* functions that construct exception objects first.
|
||||||
|
* This pulls in ~3KB of dead exception class code that can never run.
|
||||||
|
*
|
||||||
|
* ESP8266 Arduino already solved this: their toolchain rebuilds libstdc++
|
||||||
|
* with throw functions that just call abort(). We achieve the same result
|
||||||
|
* using linker --wrap without requiring toolchain changes.
|
||||||
|
*
|
||||||
|
* These stubs abort immediately with a descriptive message, allowing
|
||||||
|
* the linker to dead-code eliminate the exception class infrastructure.
|
||||||
|
*
|
||||||
|
* Wrapped functions and their callers:
|
||||||
|
* - std::__throw_length_error: std::string::reserve, std::vector::reserve
|
||||||
|
* - std::__throw_logic_error: std::promise, std::packaged_task
|
||||||
|
* - std::__throw_out_of_range: std::string::at, std::vector::at
|
||||||
|
* - std::__throw_out_of_range_fmt: std::bitset::to_ulong
|
||||||
|
* - std::__throw_bad_alloc: operator new
|
||||||
|
* - std::__throw_bad_function_call: std::function::operator()
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_ESP_IDF
|
||||||
|
#include "esp_system.h"
|
||||||
|
|
||||||
|
namespace esphome::esp32 {}
|
||||||
|
|
||||||
|
// Linker wraps for std::__throw_* - must be extern "C" at global scope.
|
||||||
|
// Names must be __wrap_ + mangled name for the linker's --wrap option.
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// std::__throw_length_error(char const*) - called when container size exceeds max_size()
|
||||||
|
void __wrap__ZSt20__throw_length_errorPKc(const char *) { esp_system_abort("std::length_error"); }
|
||||||
|
|
||||||
|
// std::__throw_logic_error(char const*) - called for logic errors (e.g., promise already satisfied)
|
||||||
|
void __wrap__ZSt19__throw_logic_errorPKc(const char *) { esp_system_abort("std::logic_error"); }
|
||||||
|
|
||||||
|
// std::__throw_out_of_range(char const*) - called by at() when index is out of bounds
|
||||||
|
void __wrap__ZSt20__throw_out_of_rangePKc(const char *) { esp_system_abort("std::out_of_range"); }
|
||||||
|
|
||||||
|
// std::__throw_out_of_range_fmt(char const*, ...) - called by bitset::to_ulong when value doesn't fit
|
||||||
|
void __wrap__ZSt24__throw_out_of_range_fmtPKcz(const char *, ...) { esp_system_abort("std::out_of_range"); }
|
||||||
|
|
||||||
|
// std::__throw_bad_alloc() - called when operator new fails
|
||||||
|
void __wrap__ZSt17__throw_bad_allocv() { esp_system_abort("std::bad_alloc"); }
|
||||||
|
|
||||||
|
// std::__throw_bad_function_call() - called when invoking empty std::function
|
||||||
|
void __wrap__ZSt25__throw_bad_function_callv() { esp_system_abort("std::bad_function_call"); }
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
|
// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp,readability-identifier-naming)
|
||||||
|
|
||||||
|
#endif // USE_ESP_IDF
|
||||||
@@ -5,6 +5,7 @@ from esphome import pins
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, light
|
from esphome.components import esp32, light
|
||||||
from esphome.components.const import CONF_USE_PSRAM
|
from esphome.components.const import CONF_USE_PSRAM
|
||||||
|
from esphome.components.esp32 import include_builtin_idf_component
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CHIPSET,
|
CONF_CHIPSET,
|
||||||
@@ -129,6 +130,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_driver_rmt")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||||
await light.register_light(var, config)
|
await light.register_light(var, config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from esphome.components.esp32 import (
|
|||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
get_esp32_variant,
|
get_esp32_variant,
|
||||||
gpio,
|
gpio,
|
||||||
|
include_builtin_idf_component,
|
||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -266,6 +267,9 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
|
# Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_driver_touch_sens")
|
||||||
|
|
||||||
touch = cg.new_Pvariable(config[CONF_ID])
|
touch = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(touch, config)
|
await cg.register_component(touch, config)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from esphome.components.esp32 import (
|
|||||||
add_idf_component,
|
add_idf_component,
|
||||||
add_idf_sdkconfig_option,
|
add_idf_sdkconfig_option,
|
||||||
get_esp32_variant,
|
get_esp32_variant,
|
||||||
|
include_builtin_idf_component,
|
||||||
)
|
)
|
||||||
from esphome.components.network import ip_address_literal
|
from esphome.components.network import ip_address_literal
|
||||||
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
||||||
@@ -419,6 +420,9 @@ async def to_code(config):
|
|||||||
# Also disable WiFi/BT coexistence since WiFi is disabled
|
# Also disable WiFi/BT coexistence since WiFi is disabled
|
||||||
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", False)
|
||||||
|
|
||||||
|
# Re-enable ESP-IDF's Ethernet driver (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_eth")
|
||||||
|
|
||||||
if config[CONF_TYPE] == "LAN8670":
|
if config[CONF_TYPE] == "LAN8670":
|
||||||
# Add LAN867x 10BASE-T1S PHY support component
|
# Add LAN867x 10BASE-T1S PHY support component
|
||||||
add_idf_component(name="espressif/lan867x", ref="2.0.0")
|
add_idf_component(name="espressif/lan867x", ref="2.0.0")
|
||||||
|
|||||||
@@ -16,12 +16,8 @@ namespace event {
|
|||||||
#define LOG_EVENT(prefix, type, obj) \
|
#define LOG_EVENT(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
LOG_ENTITY_DEVICE_CLASS(TAG, prefix, *(obj)); \
|
||||||
} \
|
|
||||||
if (!(obj)->get_device_class_ref().empty()) { \
|
|
||||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Event : public EntityBase, public EntityBase_DeviceClass {
|
class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@ static bool was_power_cycled() {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
auto reset_reason = EspClass::getResetReason();
|
auto reset_reason = EspClass::getResetReason();
|
||||||
return strcasecmp(reset_reason.c_str(), "power On") == 0 || strcasecmp(reset_reason.c_str(), "external system") == 0;
|
return ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("power On")) == 0 ||
|
||||||
|
ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("external system")) == 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIBRETINY
|
#ifdef USE_LIBRETINY
|
||||||
auto reason = lt_get_reboot_reason();
|
auto reason = lt_get_reboot_reason();
|
||||||
|
|||||||
@@ -227,8 +227,7 @@ void Fan::publish_state() {
|
|||||||
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
|
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
|
||||||
optional<FanRestoreState> Fan::restore_state_() {
|
optional<FanRestoreState> Fan::restore_state_() {
|
||||||
FanRestoreState recovered{};
|
FanRestoreState recovered{};
|
||||||
this->rtc_ =
|
this->rtc_ = this->make_entity_preference<FanRestoreState>(RESTORE_STATE_VERSION);
|
||||||
global_preferences->make_preference<FanRestoreState>(this->get_preference_hash() ^ RESTORE_STATE_VERSION);
|
|
||||||
bool restored = this->rtc_.load(&recovered);
|
bool restored = this->rtc_.load(&recovered);
|
||||||
|
|
||||||
switch (this->restore_mode_) {
|
switch (this->restore_mode_) {
|
||||||
|
|||||||
@@ -9,30 +9,56 @@ from esphome.const import (
|
|||||||
CONF_VALUE,
|
CONF_VALUE,
|
||||||
)
|
)
|
||||||
from esphome.core import CoroPriority, coroutine_with_priority
|
from esphome.core import CoroPriority, coroutine_with_priority
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
globals_ns = cg.esphome_ns.namespace("globals")
|
globals_ns = cg.esphome_ns.namespace("globals")
|
||||||
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
|
GlobalsComponent = globals_ns.class_("GlobalsComponent", cg.Component)
|
||||||
RestoringGlobalsComponent = globals_ns.class_("RestoringGlobalsComponent", cg.Component)
|
RestoringGlobalsComponent = globals_ns.class_(
|
||||||
|
"RestoringGlobalsComponent", cg.PollingComponent
|
||||||
|
)
|
||||||
RestoringGlobalStringComponent = globals_ns.class_(
|
RestoringGlobalStringComponent = globals_ns.class_(
|
||||||
"RestoringGlobalStringComponent", cg.Component
|
"RestoringGlobalStringComponent", cg.PollingComponent
|
||||||
)
|
)
|
||||||
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
|
GlobalVarSetAction = globals_ns.class_("GlobalVarSetAction", automation.Action)
|
||||||
|
|
||||||
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
|
CONF_MAX_RESTORE_DATA_LENGTH = "max_restore_data_length"
|
||||||
|
|
||||||
|
# Base schema fields shared by both variants
|
||||||
|
_BASE_SCHEMA = {
|
||||||
|
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
||||||
|
cv.Required(CONF_TYPE): cv.string_strict,
|
||||||
|
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
||||||
|
cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
|
||||||
|
}
|
||||||
|
|
||||||
MULTI_CONF = True
|
# Non-restoring globals: regular Component (no polling needed)
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
_NON_RESTORING_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_ID): cv.declare_id(GlobalsComponent),
|
**_BASE_SCHEMA,
|
||||||
cv.Required(CONF_TYPE): cv.string_strict,
|
|
||||||
cv.Optional(CONF_INITIAL_VALUE): cv.string_strict,
|
|
||||||
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
cv.Optional(CONF_RESTORE_VALUE, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_MAX_RESTORE_DATA_LENGTH): cv.int_range(0, 254),
|
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA)
|
).extend(cv.COMPONENT_SCHEMA)
|
||||||
|
|
||||||
|
# Restoring globals: PollingComponent with configurable update_interval
|
||||||
|
_RESTORING_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
**_BASE_SCHEMA,
|
||||||
|
cv.Optional(CONF_RESTORE_VALUE, default=True): cv.boolean,
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("1s"))
|
||||||
|
|
||||||
|
|
||||||
|
def _globals_schema(config: ConfigType) -> ConfigType:
|
||||||
|
"""Select schema based on restore_value setting."""
|
||||||
|
if config.get(CONF_RESTORE_VALUE, False):
|
||||||
|
return _RESTORING_SCHEMA(config)
|
||||||
|
return _NON_RESTORING_SCHEMA(config)
|
||||||
|
|
||||||
|
|
||||||
|
MULTI_CONF = True
|
||||||
|
CONFIG_SCHEMA = _globals_schema
|
||||||
|
|
||||||
|
|
||||||
# Run with low priority so that namespaces are registered first
|
# Run with low priority so that namespaces are registered first
|
||||||
@coroutine_with_priority(CoroPriority.LATE)
|
@coroutine_with_priority(CoroPriority.LATE)
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::globals {
|
||||||
namespace globals {
|
|
||||||
|
|
||||||
template<typename T> class GlobalsComponent : public Component {
|
template<typename T> class GlobalsComponent : public Component {
|
||||||
public:
|
public:
|
||||||
@@ -24,13 +23,14 @@ template<typename T> class GlobalsComponent : public Component {
|
|||||||
T value_{};
|
T value_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T> class RestoringGlobalsComponent : public Component {
|
template<typename T> class RestoringGlobalsComponent : public PollingComponent {
|
||||||
public:
|
public:
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
explicit RestoringGlobalsComponent() = default;
|
explicit RestoringGlobalsComponent() : PollingComponent(1000) {}
|
||||||
explicit RestoringGlobalsComponent(T initial_value) : value_(initial_value) {}
|
explicit RestoringGlobalsComponent(T initial_value) : PollingComponent(1000), value_(initial_value) {}
|
||||||
explicit RestoringGlobalsComponent(
|
explicit RestoringGlobalsComponent(
|
||||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value)
|
||||||
|
: PollingComponent(1000) {
|
||||||
memcpy(this->value_, initial_value.data(), sizeof(T));
|
memcpy(this->value_, initial_value.data(), sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ template<typename T> class RestoringGlobalsComponent : public Component {
|
|||||||
|
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
void loop() override { store_value_(); }
|
void update() override { store_value_(); }
|
||||||
|
|
||||||
void on_shutdown() override { store_value_(); }
|
void on_shutdown() override { store_value_(); }
|
||||||
|
|
||||||
@@ -66,13 +66,14 @@ template<typename T> class RestoringGlobalsComponent : public Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Use with string or subclasses of strings
|
// Use with string or subclasses of strings
|
||||||
template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public Component {
|
template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public PollingComponent {
|
||||||
public:
|
public:
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
explicit RestoringGlobalStringComponent() = default;
|
explicit RestoringGlobalStringComponent() : PollingComponent(1000) {}
|
||||||
explicit RestoringGlobalStringComponent(T initial_value) { this->value_ = initial_value; }
|
explicit RestoringGlobalStringComponent(T initial_value) : PollingComponent(1000) { this->value_ = initial_value; }
|
||||||
explicit RestoringGlobalStringComponent(
|
explicit RestoringGlobalStringComponent(
|
||||||
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value) {
|
std::array<typename std::remove_extent<T>::type, std::extent<T>::value> initial_value)
|
||||||
|
: PollingComponent(1000) {
|
||||||
memcpy(this->value_, initial_value.data(), sizeof(T));
|
memcpy(this->value_, initial_value.data(), sizeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ template<typename T, uint8_t SZ> class RestoringGlobalStringComponent : public C
|
|||||||
|
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
void loop() override { store_value_(); }
|
void update() override { store_value_(); }
|
||||||
|
|
||||||
void on_shutdown() override { store_value_(); }
|
void on_shutdown() override { store_value_(); }
|
||||||
|
|
||||||
@@ -144,5 +145,4 @@ template<typename T> T &id(GlobalsComponent<T> *value) { return value->value();
|
|||||||
template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
|
template<typename T> T &id(RestoringGlobalsComponent<T> *value) { return value->value(); }
|
||||||
template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
|
template<typename T, uint8_t SZ> T &id(RestoringGlobalStringComponent<T, SZ> *value) { return value->value(); }
|
||||||
|
|
||||||
} // namespace globals
|
} // namespace esphome::globals
|
||||||
} // namespace esphome
|
|
||||||
|
|||||||
@@ -350,8 +350,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
|
|||||||
|
|
||||||
void HaierClimateBase::initialization() {
|
void HaierClimateBase::initialization() {
|
||||||
constexpr uint32_t restore_settings_version = 0xA77D21EF;
|
constexpr uint32_t restore_settings_version = 0xA77D21EF;
|
||||||
this->base_rtc_ =
|
this->base_rtc_ = this->make_entity_preference<HaierBaseSettings>(restore_settings_version);
|
||||||
global_preferences->make_preference<HaierBaseSettings>(this->get_preference_hash() ^ restore_settings_version);
|
|
||||||
HaierBaseSettings recovered;
|
HaierBaseSettings recovered;
|
||||||
if (!this->base_rtc_.load(&recovered)) {
|
if (!this->base_rtc_.load(&recovered)) {
|
||||||
recovered = {false, true};
|
recovered = {false, true};
|
||||||
|
|||||||
@@ -515,8 +515,7 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
|
|||||||
void HonClimate::initialization() {
|
void HonClimate::initialization() {
|
||||||
HaierClimateBase::initialization();
|
HaierClimateBase::initialization();
|
||||||
constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
|
constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
|
||||||
this->hon_rtc_ =
|
this->hon_rtc_ = this->make_entity_preference<HonSettings>(restore_settings_version);
|
||||||
global_preferences->make_preference<HonSettings>(this->get_preference_hash() ^ restore_settings_version);
|
|
||||||
HonSettings recovered;
|
HonSettings recovered;
|
||||||
if (this->hon_rtc_.load(&recovered)) {
|
if (this->hon_rtc_.load(&recovered)) {
|
||||||
this->settings_ = recovered;
|
this->settings_ = recovered;
|
||||||
|
|||||||
@@ -155,6 +155,9 @@ async def to_code(config):
|
|||||||
cg.add(var.set_watchdog_timeout(timeout_ms))
|
cg.add(var.set_watchdog_timeout(timeout_ms))
|
||||||
|
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
|
||||||
|
esp32.include_builtin_idf_component("esp_http_client")
|
||||||
|
|
||||||
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
|
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
|
||||||
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
|
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
|
||||||
cg.add(var.set_verify_ssl(config[CONF_VERIFY_SSL]))
|
cg.add(var.set_verify_ssl(config[CONF_VERIFY_SSL]))
|
||||||
@@ -165,6 +168,16 @@ async def to_code(config):
|
|||||||
ca_cert_content = f.read()
|
ca_cert_content = f.read()
|
||||||
cg.add(var.set_ca_certificate(ca_cert_content))
|
cg.add(var.set_ca_certificate(ca_cert_content))
|
||||||
else:
|
else:
|
||||||
|
# Uses the certificate bundle configured in esp32 component.
|
||||||
|
# By default, ESPHome uses the CMN (common CAs) bundle which covers
|
||||||
|
# ~99% of websites including GitHub, Let's Encrypt, DigiCert, etc.
|
||||||
|
# If connecting to services with uncommon CAs, components can call:
|
||||||
|
# esp32.require_full_certificate_bundle()
|
||||||
|
# Or users can set in their config:
|
||||||
|
# esp32:
|
||||||
|
# framework:
|
||||||
|
# advanced:
|
||||||
|
# use_full_certificate_bundle: true
|
||||||
esp32.add_idf_sdkconfig_option(
|
esp32.add_idf_sdkconfig_option(
|
||||||
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True
|
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -131,6 +131,10 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPClient::getSize() returns -1 for chunked transfer encoding (no Content-Length).
|
||||||
|
// When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit).
|
||||||
|
// The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the
|
||||||
|
// early return check (bytes_read_ >= content_length) will never trigger.
|
||||||
int content_length = container->client_.getSize();
|
int content_length = container->client_.getSize();
|
||||||
ESP_LOGD(TAG, "Content-Length: %d", content_length);
|
ESP_LOGD(TAG, "Content-Length: %d", content_length);
|
||||||
container->content_length = (size_t) content_length;
|
container->content_length = (size_t) content_length;
|
||||||
@@ -167,17 +171,23 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int available_data = stream_ptr->available();
|
int available_data = stream_ptr->available();
|
||||||
int bufsize = std::min(max_len, std::min(this->content_length - this->bytes_read_, (size_t) available_data));
|
// For chunked transfer encoding, HTTPClient::getSize() returns -1, which becomes SIZE_MAX when
|
||||||
|
// cast to size_t. SIZE_MAX - bytes_read_ is still huge, so it won't limit the read.
|
||||||
|
size_t remaining = (this->content_length > 0) ? (this->content_length - this->bytes_read_) : max_len;
|
||||||
|
int bufsize = std::min(max_len, std::min(remaining, (size_t) available_data));
|
||||||
|
|
||||||
if (bufsize == 0) {
|
if (bufsize == 0) {
|
||||||
this->duration_ms += (millis() - start);
|
this->duration_ms += (millis() - start);
|
||||||
// Check if we've read all expected content
|
// Check if we've read all expected content (only valid when content_length is known and not SIZE_MAX)
|
||||||
if (this->bytes_read_ >= this->content_length) {
|
// For chunked encoding (content_length == SIZE_MAX), we can't use this check
|
||||||
|
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
|
||||||
return 0; // All content read successfully
|
return 0; // All content read successfully
|
||||||
}
|
}
|
||||||
// No data available - check if connection is still open
|
// No data available - check if connection is still open
|
||||||
|
// For chunked encoding, !connected() after reading means EOF (all chunks received)
|
||||||
|
// For known content_length with bytes_read_ < content_length, it means connection dropped
|
||||||
if (!stream_ptr->connected()) {
|
if (!stream_ptr->connected()) {
|
||||||
return HTTP_ERROR_CONNECTION_CLOSED; // Connection closed prematurely
|
return HTTP_ERROR_CONNECTION_CLOSED; // Connection closed or EOF for chunked
|
||||||
}
|
}
|
||||||
return 0; // No data yet, caller should retry
|
return 0; // No data yet, caller should retry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
container->feed_wdt();
|
container->feed_wdt();
|
||||||
|
// esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header).
|
||||||
|
// The read() method handles content_length == 0 specially to support chunked responses.
|
||||||
container->content_length = esp_http_client_fetch_headers(client);
|
container->content_length = esp_http_client_fetch_headers(client);
|
||||||
container->feed_wdt();
|
container->feed_wdt();
|
||||||
container->status_code = esp_http_client_get_status_code(client);
|
container->status_code = esp_http_client_get_status_code(client);
|
||||||
@@ -225,14 +227,22 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
|||||||
//
|
//
|
||||||
// We normalize to HttpContainer::read() contract:
|
// We normalize to HttpContainer::read() contract:
|
||||||
// > 0: bytes read
|
// > 0: bytes read
|
||||||
// 0: no data yet / all content read (caller should check bytes_read vs content_length)
|
// 0: all content read (only returned when content_length is known and fully read)
|
||||||
// < 0: error/connection closed
|
// < 0: error/connection closed
|
||||||
|
//
|
||||||
|
// Note on chunked transfer encoding:
|
||||||
|
// esp_http_client_fetch_headers() returns 0 for chunked responses (no Content-Length header).
|
||||||
|
// We handle this by skipping the content_length check when content_length is 0,
|
||||||
|
// allowing esp_http_client_read() to handle chunked decoding internally and signal EOF
|
||||||
|
// by returning 0.
|
||||||
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||||
const uint32_t start = millis();
|
const uint32_t start = millis();
|
||||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||||
|
|
||||||
// Check if we've already read all expected content
|
// Check if we've already read all expected content
|
||||||
if (this->bytes_read_ >= this->content_length) {
|
// Skip this check when content_length is 0 (chunked transfer encoding or unknown length)
|
||||||
|
// For chunked responses, esp_http_client_read() will return 0 when all data is received
|
||||||
|
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
|
||||||
return 0; // All content read successfully
|
return 0; // All content read successfully
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +257,13 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
|||||||
return read_len_or_error;
|
return read_len_or_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection closed by server before all content received
|
// esp_http_client_read() returns 0 in two cases:
|
||||||
|
// 1. Known content_length: connection closed before all data received (error)
|
||||||
|
// 2. Chunked encoding (content_length == 0): end of stream reached (EOF)
|
||||||
|
// For case 1, returning HTTP_ERROR_CONNECTION_CLOSED is correct.
|
||||||
|
// For case 2, 0 indicates that all chunked data has already been delivered
|
||||||
|
// in previous successful read() calls, so treating this as a closed
|
||||||
|
// connection does not cause any loss of response data.
|
||||||
if (read_len_or_error == 0) {
|
if (read_len_or_error == 0) {
|
||||||
return HTTP_ERROR_CONNECTION_CLOSED;
|
return HTTP_ERROR_CONNECTION_CLOSED;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
from . import BoardConfig
|
from . import BoardConfig
|
||||||
|
|
||||||
|
# Huidu HD-WF1
|
||||||
|
BoardConfig(
|
||||||
|
"huidu-hd-wf1",
|
||||||
|
r1_pin=2,
|
||||||
|
g1_pin=6,
|
||||||
|
b1_pin=3,
|
||||||
|
r2_pin=4,
|
||||||
|
g2_pin=8,
|
||||||
|
b2_pin=5,
|
||||||
|
a_pin=39,
|
||||||
|
b_pin=38,
|
||||||
|
c_pin=37,
|
||||||
|
d_pin=36,
|
||||||
|
e_pin=12,
|
||||||
|
lat_pin=33,
|
||||||
|
oe_pin=35,
|
||||||
|
clk_pin=34,
|
||||||
|
)
|
||||||
|
|
||||||
# Huidu HD-WF2
|
# Huidu HD-WF2
|
||||||
BoardConfig(
|
BoardConfig(
|
||||||
"huidu-hd-wf2",
|
"huidu-hd-wf2",
|
||||||
|
|||||||
@@ -587,7 +587,7 @@ def _build_config_struct(
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
add_idf_component(
|
add_idf_component(
|
||||||
name="esphome/esp-hub75",
|
name="esphome/esp-hub75",
|
||||||
ref="0.3.0",
|
ref="0.3.2",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set compile-time configuration via build flags (so external library sees them)
|
# Set compile-time configuration via build flags (so external library sees them)
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components.esp32 import (
|
from esphome.components.esp32 import (
|
||||||
|
add_idf_sdkconfig_option,
|
||||||
|
get_esp32_variant,
|
||||||
|
include_builtin_idf_component,
|
||||||
|
)
|
||||||
|
from esphome.components.esp32.const import (
|
||||||
VARIANT_ESP32,
|
VARIANT_ESP32,
|
||||||
VARIANT_ESP32C3,
|
VARIANT_ESP32C3,
|
||||||
VARIANT_ESP32C5,
|
VARIANT_ESP32C5,
|
||||||
@@ -10,8 +15,6 @@ from esphome.components.esp32 import (
|
|||||||
VARIANT_ESP32P4,
|
VARIANT_ESP32P4,
|
||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
add_idf_sdkconfig_option,
|
|
||||||
get_esp32_variant,
|
|
||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
|
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
|
||||||
@@ -272,6 +275,10 @@ FINAL_VALIDATE_SCHEMA = _final_validate
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
# Re-enable ESP-IDF's I2S driver (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("esp_driver_i2s")
|
||||||
|
|
||||||
if use_legacy():
|
if use_legacy():
|
||||||
cg.add_define("USE_I2S_LEGACY")
|
cg.add_define("USE_I2S_LEGACY")
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ static const char *const TAG = "integration";
|
|||||||
|
|
||||||
void IntegrationSensor::setup() {
|
void IntegrationSensor::setup() {
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<float>();
|
||||||
float preference_value = 0;
|
float preference_value = 0;
|
||||||
this->pref_.load(&preference_value);
|
this->pref_.load(&preference_value);
|
||||||
this->result_ = preference_value;
|
this->result_ = preference_value;
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
|
|||||||
void LD2450Component::setup() {
|
void LD2450Component::setup() {
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
if (this->presence_timeout_number_ != nullptr) {
|
if (this->presence_timeout_number_ != nullptr) {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_preference_hash());
|
this->pref_ = this->presence_timeout_number_->make_entity_preference<float>();
|
||||||
this->set_presence_timeout();
|
this->set_presence_timeout();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -451,7 +451,7 @@ void LD2450Component::handle_periodic_data_() {
|
|||||||
int16_t ty = 0;
|
int16_t ty = 0;
|
||||||
int16_t td = 0;
|
int16_t td = 0;
|
||||||
int16_t ts = 0;
|
int16_t ts = 0;
|
||||||
int16_t angle = 0;
|
float angle = 0;
|
||||||
uint8_t index = 0;
|
uint8_t index = 0;
|
||||||
Direction direction{DIRECTION_UNDEFINED};
|
Direction direction{DIRECTION_UNDEFINED};
|
||||||
bool is_moving = false;
|
bool is_moving = false;
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
|||||||
],
|
],
|
||||||
icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
|
icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
|
||||||
unit_of_measurement=UNIT_DEGREES,
|
unit_of_measurement=UNIT_DEGREES,
|
||||||
|
accuracy_decimals=1,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ void LightState::setup() {
|
|||||||
case LIGHT_RESTORE_DEFAULT_ON:
|
case LIGHT_RESTORE_DEFAULT_ON:
|
||||||
case LIGHT_RESTORE_INVERTED_DEFAULT_OFF:
|
case LIGHT_RESTORE_INVERTED_DEFAULT_OFF:
|
||||||
case LIGHT_RESTORE_INVERTED_DEFAULT_ON:
|
case LIGHT_RESTORE_INVERTED_DEFAULT_ON:
|
||||||
this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash());
|
this->rtc_ = this->make_entity_preference<LightStateRTCState>();
|
||||||
// Attempt to load from preferences, else fall back to default values
|
// Attempt to load from preferences, else fall back to default values
|
||||||
if (!this->rtc_.load(&recovered)) {
|
if (!this->rtc_.load(&recovered)) {
|
||||||
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
|
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
|
||||||
@@ -57,7 +57,7 @@ void LightState::setup() {
|
|||||||
break;
|
break;
|
||||||
case LIGHT_RESTORE_AND_OFF:
|
case LIGHT_RESTORE_AND_OFF:
|
||||||
case LIGHT_RESTORE_AND_ON:
|
case LIGHT_RESTORE_AND_ON:
|
||||||
this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash());
|
this->rtc_ = this->make_entity_preference<LightStateRTCState>();
|
||||||
this->rtc_.load(&recovered);
|
this->rtc_.load(&recovered);
|
||||||
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
|
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ class Lock;
|
|||||||
#define LOG_LOCK(prefix, type, obj) \
|
#define LOG_LOCK(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
if ((obj)->traits.get_assumed_state()) { \
|
if ((obj)->traits.get_assumed_state()) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||||
} \
|
} \
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class LVGLNumber : public number::Number, public Component {
|
|||||||
void setup() override {
|
void setup() override {
|
||||||
float value = this->value_lambda_();
|
float value = this->value_lambda_();
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<float>();
|
||||||
if (this->pref_.load(&value)) {
|
if (this->pref_.load(&value)) {
|
||||||
this->control_lambda_(value);
|
this->control_lambda_(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class LVGLSelect : public select::Select, public Component {
|
|||||||
this->set_options_();
|
this->set_options_();
|
||||||
if (this->restore_) {
|
if (this->restore_) {
|
||||||
size_t index;
|
size_t index;
|
||||||
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<size_t>();
|
||||||
if (this->pref_.load(&index))
|
if (this->pref_.load(&index))
|
||||||
this->widget_->set_selected_index(index, LV_ANIM_OFF);
|
this->widget_->set_selected_index(index, LV_ANIM_OFF);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ namespace esphome::mdns {
|
|||||||
static const char *const TAG = "mdns";
|
static const char *const TAG = "mdns";
|
||||||
|
|
||||||
static void register_esp32(MDNSComponent *comp, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
static void register_esp32(MDNSComponent *comp, StaticVector<MDNSService, MDNS_SERVICE_COUNT> &services) {
|
||||||
|
#ifdef USE_OPENTHREAD
|
||||||
|
// OpenThread handles service registration via SRP client
|
||||||
|
// Services are compiled by MDNSComponent::compile_records_() and consumed by OpenThreadSrpComponent
|
||||||
|
#else
|
||||||
esp_err_t err = mdns_init();
|
esp_err_t err = mdns_init();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "Init failed: %s", esp_err_to_name(err));
|
ESP_LOGW(TAG, "Init failed: %s", esp_err_to_name(err));
|
||||||
@@ -41,13 +45,16 @@ static void register_esp32(MDNSComponent *comp, StaticVector<MDNSService, MDNS_S
|
|||||||
ESP_LOGW(TAG, "Failed to register service %s: %s", MDNS_STR_ARG(service.service_type), esp_err_to_name(err));
|
ESP_LOGW(TAG, "Failed to register service %s: %s", MDNS_STR_ARG(service.service_type), esp_err_to_name(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp32); }
|
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp32); }
|
||||||
|
|
||||||
void MDNSComponent::on_shutdown() {
|
void MDNSComponent::on_shutdown() {
|
||||||
|
#ifndef USE_OPENTHREAD
|
||||||
mdns_free();
|
mdns_free();
|
||||||
delay(40); // Allow the mdns packets announcing service removal to be sent
|
delay(40); // Allow the mdns packets announcing service removal to be sent
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::mdns
|
} // namespace esphome::mdns
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ from esphome import automation
|
|||||||
from esphome.automation import Condition
|
from esphome.automation import Condition
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import logger, socket
|
from esphome.components import logger, socket
|
||||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
from esphome.components.esp32 import (
|
||||||
|
add_idf_sdkconfig_option,
|
||||||
|
include_builtin_idf_component,
|
||||||
|
)
|
||||||
from esphome.config_helpers import filter_source_files_from_platform
|
from esphome.config_helpers import filter_source_files_from_platform
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -360,6 +363,8 @@ async def to_code(config):
|
|||||||
# This enables low-latency MQTT event processing instead of waiting for select() timeout
|
# This enables low-latency MQTT event processing instead of waiting for select() timeout
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
socket.require_wake_loop_threadsafe()
|
socket.require_wake_loop_threadsafe()
|
||||||
|
# Re-enable ESP-IDF's mqtt component (excluded by default to save compile time)
|
||||||
|
include_builtin_idf_component("mqtt")
|
||||||
|
|
||||||
cg.add_define("USE_MQTT")
|
cg.add_define("USE_MQTT")
|
||||||
cg.add_global(mqtt_ns.using)
|
cg.add_global(mqtt_ns.using)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "mqtt_alarm_control_panel.h"
|
#include "mqtt_alarm_control_panel.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
#include "mqtt_const.h"
|
#include "mqtt_const.h"
|
||||||
|
|
||||||
@@ -18,21 +19,21 @@ void MQTTAlarmControlPanelComponent::setup() {
|
|||||||
this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); });
|
this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); });
|
||||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||||
auto call = this->alarm_control_panel_->make_call();
|
auto call = this->alarm_control_panel_->make_call();
|
||||||
if (strcasecmp(payload.c_str(), "ARM_AWAY") == 0) {
|
if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_AWAY")) == 0) {
|
||||||
call.arm_away();
|
call.arm_away();
|
||||||
} else if (strcasecmp(payload.c_str(), "ARM_HOME") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_HOME")) == 0) {
|
||||||
call.arm_home();
|
call.arm_home();
|
||||||
} else if (strcasecmp(payload.c_str(), "ARM_NIGHT") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_NIGHT")) == 0) {
|
||||||
call.arm_night();
|
call.arm_night();
|
||||||
} else if (strcasecmp(payload.c_str(), "ARM_VACATION") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_VACATION")) == 0) {
|
||||||
call.arm_vacation();
|
call.arm_vacation();
|
||||||
} else if (strcasecmp(payload.c_str(), "ARM_CUSTOM_BYPASS") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_CUSTOM_BYPASS")) == 0) {
|
||||||
call.arm_custom_bypass();
|
call.arm_custom_bypass();
|
||||||
} else if (strcasecmp(payload.c_str(), "DISARM") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("DISARM")) == 0) {
|
||||||
call.disarm();
|
call.disarm();
|
||||||
} else if (strcasecmp(payload.c_str(), "PENDING") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("PENDING")) == 0) {
|
||||||
call.pending();
|
call.pending();
|
||||||
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("TRIGGERED")) == 0) {
|
||||||
call.triggered();
|
call.triggered();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());
|
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "mqtt_lock.h"
|
#include "mqtt_lock.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
#include "mqtt_const.h"
|
#include "mqtt_const.h"
|
||||||
|
|
||||||
@@ -16,11 +17,11 @@ MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {}
|
|||||||
|
|
||||||
void MQTTLockComponent::setup() {
|
void MQTTLockComponent::setup() {
|
||||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||||
if (strcasecmp(payload.c_str(), "LOCK") == 0) {
|
if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("LOCK")) == 0) {
|
||||||
this->lock_->lock();
|
this->lock_->lock();
|
||||||
} else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("UNLOCK")) == 0) {
|
||||||
this->lock_->unlock();
|
this->lock_->unlock();
|
||||||
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
} else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("OPEN")) == 0) {
|
||||||
this->lock_->open();
|
this->lock_->open();
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
from esphome import pins
|
from esphome import pins
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import light
|
from esphome.components import light
|
||||||
from esphome.components.esp32 import VARIANT_ESP32C3, VARIANT_ESP32S3, get_esp32_variant
|
from esphome.components.esp32 import (
|
||||||
|
VARIANT_ESP32C3,
|
||||||
|
VARIANT_ESP32S3,
|
||||||
|
get_esp32_variant,
|
||||||
|
include_builtin_idf_component,
|
||||||
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CHANNEL,
|
CONF_CHANNEL,
|
||||||
@@ -205,6 +210,10 @@ async def to_code(config):
|
|||||||
has_white = "W" in config[CONF_TYPE]
|
has_white = "W" in config[CONF_TYPE]
|
||||||
method = config[CONF_METHOD]
|
method = config[CONF_METHOD]
|
||||||
|
|
||||||
|
# Re-enable ESP-IDF's RMT driver if using RMT method (excluded by default)
|
||||||
|
if CORE.is_esp32 and method[CONF_TYPE] == METHOD_ESP32_RMT:
|
||||||
|
include_builtin_idf_component("esp_driver_rmt")
|
||||||
|
|
||||||
method_template = METHODS[method[CONF_TYPE]].to_code(
|
method_template = METHODS[method[CONF_TYPE]].to_code(
|
||||||
method, config[CONF_VARIANT], config[CONF_INVERT]
|
method, config[CONF_VARIANT], config[CONF_INVERT]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ async def to_code(config):
|
|||||||
cg.add_define("USE_NEXTION_TFT_UPLOAD")
|
cg.add_define("USE_NEXTION_TFT_UPLOAD")
|
||||||
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
|
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's HTTP client (excluded by default to save compile time)
|
||||||
|
esp32.include_builtin_idf_component("esp_http_client")
|
||||||
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
|
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
|
||||||
esp32.add_idf_sdkconfig_option(
|
esp32.add_idf_sdkconfig_option(
|
||||||
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
|
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
|
||||||
|
|||||||
@@ -150,27 +150,24 @@ void Nextion::dump_config() {
|
|||||||
#ifdef USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
#ifdef USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
||||||
ESP_LOGCONFIG(TAG, " Skip handshake: YES");
|
ESP_LOGCONFIG(TAG, " Skip handshake: YES");
|
||||||
#else // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
#else // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
" Device Model: %s\n"
|
" Device Model: %s\n"
|
||||||
" FW Version: %s\n"
|
" FW Version: %s\n"
|
||||||
" Serial Number: %s\n"
|
" Serial Number: %s\n"
|
||||||
" Flash Size: %s\n"
|
" Flash Size: %s\n"
|
||||||
" Max queue age: %u ms\n"
|
" Max queue age: %u ms\n"
|
||||||
" Startup override: %u ms\n",
|
" Startup override: %u ms\n",
|
||||||
|
this->device_model_.c_str(), this->firmware_version_.c_str(), this->serial_number_.c_str(),
|
||||||
|
this->flash_size_.c_str(), this->max_q_age_ms_, this->startup_override_ms_);
|
||||||
#endif // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
#endif // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
||||||
#ifdef USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
|
#ifdef USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
|
||||||
" Exit reparse: YES\n"
|
ESP_LOGCONFIG(TAG, " Exit reparse: YES\n");
|
||||||
#endif // USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
|
#endif // USE_NEXTION_CONFIG_EXIT_REPARSE_ON_START
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
" Wake On Touch: %s\n"
|
" Wake On Touch: %s\n"
|
||||||
" Touch Timeout: %" PRIu16,
|
" Touch Timeout: %" PRIu16,
|
||||||
#ifdef USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
YESNO(this->connection_state_.auto_wake_on_touch_), this->touch_sleep_timeout_);
|
||||||
this->device_model_.c_str(), this->firmware_version_.c_str(), this->serial_number_.c_str(),
|
|
||||||
this->flash_size_.c_str(), this->max_q_age_ms_,
|
|
||||||
this->startup_override_ms_
|
|
||||||
#endif // USE_NEXTION_CONFIG_DUMP_DEVICE_INFO
|
|
||||||
YESNO(this->connection_state_.auto_wake_on_touch_),
|
|
||||||
this->touch_sleep_timeout_);
|
|
||||||
#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
#endif // USE_NEXTION_CONFIG_SKIP_CONNECTION_HANDSHAKE
|
||||||
|
|
||||||
#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ void NfcTagBinarySensor::set_tag_name(const std::string &str) {
|
|||||||
this->match_tag_name_ = true;
|
this->match_tag_name_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NfcTagBinarySensor::set_uid(const std::vector<uint8_t> &uid) { this->uid_ = uid; }
|
void NfcTagBinarySensor::set_uid(const NfcTagUid &uid) { this->uid_ = uid; }
|
||||||
|
|
||||||
bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) {
|
bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) {
|
||||||
for (const auto &record : msg->get_records()) {
|
for (const auto &record : msg->get_records()) {
|
||||||
@@ -63,7 +63,7 @@ bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr<NdefMessage> &
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NfcTagBinarySensor::tag_match_uid(const std::vector<uint8_t> &data) {
|
bool NfcTagBinarySensor::tag_match_uid(const NfcTagUid &data) {
|
||||||
if (data.size() != this->uid_.size()) {
|
if (data.size() != this->uid_.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ class NfcTagBinarySensor : public binary_sensor::BinarySensor,
|
|||||||
|
|
||||||
void set_ndef_match_string(const std::string &str);
|
void set_ndef_match_string(const std::string &str);
|
||||||
void set_tag_name(const std::string &str);
|
void set_tag_name(const std::string &str);
|
||||||
void set_uid(const std::vector<uint8_t> &uid);
|
void set_uid(const NfcTagUid &uid);
|
||||||
|
|
||||||
bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg);
|
bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg);
|
||||||
bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg);
|
bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg);
|
||||||
bool tag_match_uid(const std::vector<uint8_t> &data);
|
bool tag_match_uid(const NfcTagUid &data);
|
||||||
|
|
||||||
void tag_off(NfcTag &tag) override;
|
void tag_off(NfcTag &tag) override;
|
||||||
void tag_on(NfcTag &tag) override;
|
void tag_on(NfcTag &tag) override;
|
||||||
@@ -31,7 +31,7 @@ class NfcTagBinarySensor : public binary_sensor::BinarySensor,
|
|||||||
protected:
|
protected:
|
||||||
bool match_tag_name_{false};
|
bool match_tag_name_{false};
|
||||||
std::string match_string_;
|
std::string match_string_;
|
||||||
std::vector<uint8_t> uid_;
|
NfcTagUid uid_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace nfc
|
} // namespace nfc
|
||||||
|
|||||||
@@ -8,19 +8,23 @@ namespace nfc {
|
|||||||
|
|
||||||
static const char *const TAG = "nfc";
|
static const char *const TAG = "nfc";
|
||||||
|
|
||||||
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid) {
|
char *format_uid_to(char *buffer, std::span<const uint8_t> uid) {
|
||||||
return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-');
|
return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes) {
|
char *format_bytes_to(char *buffer, std::span<const uint8_t> bytes) {
|
||||||
return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' ');
|
return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
// Deprecated wrappers intentionally use heap-allocating version for backward compatibility
|
// Deprecated wrappers intentionally use heap-allocating version for backward compatibility
|
||||||
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); } // NOLINT
|
std::string format_uid(std::span<const uint8_t> uid) {
|
||||||
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); } // NOLINT
|
return format_hex_pretty(uid.data(), uid.size(), '-', false); // NOLINT
|
||||||
|
}
|
||||||
|
std::string format_bytes(std::span<const uint8_t> bytes) {
|
||||||
|
return format_hex_pretty(bytes.data(), bytes.size(), ' ', false); // NOLINT
|
||||||
|
}
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
uint8_t guess_tag_type(uint8_t uid_length) {
|
uint8_t guess_tag_type(uint8_t uid_length) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "ndef_record.h"
|
#include "ndef_record.h"
|
||||||
#include "nfc_tag.h"
|
#include "nfc_tag.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -56,19 +57,19 @@ static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
|||||||
/// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars
|
/// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars
|
||||||
static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30;
|
static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30;
|
||||||
/// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use.
|
/// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use.
|
||||||
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid);
|
char *format_uid_to(char *buffer, std::span<const uint8_t> uid);
|
||||||
|
|
||||||
/// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator)
|
/// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator)
|
||||||
static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192;
|
static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192;
|
||||||
/// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
|
/// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
|
||||||
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes);
|
char *format_bytes_to(char *buffer, std::span<const uint8_t> bytes);
|
||||||
|
|
||||||
// Remove before 2026.6.0
|
// Remove before 2026.6.0
|
||||||
ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
||||||
std::string format_uid(const std::vector<uint8_t> &uid);
|
std::string format_uid(std::span<const uint8_t> uid);
|
||||||
// Remove before 2026.6.0
|
// Remove before 2026.6.0
|
||||||
ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
||||||
std::string format_bytes(const std::vector<uint8_t> &bytes);
|
std::string format_bytes(std::span<const uint8_t> bytes);
|
||||||
|
|
||||||
uint8_t guess_tag_type(uint8_t uid_length);
|
uint8_t guess_tag_type(uint8_t uid_length);
|
||||||
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
|
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);
|
||||||
|
|||||||
@@ -10,26 +10,27 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace nfc {
|
namespace nfc {
|
||||||
|
|
||||||
|
// NFC UIDs are 4, 7, or 10 bytes depending on tag type
|
||||||
|
static constexpr size_t NFC_UID_MAX_LENGTH = 10;
|
||||||
|
using NfcTagUid = StaticVector<uint8_t, NFC_UID_MAX_LENGTH>;
|
||||||
|
|
||||||
class NfcTag {
|
class NfcTag {
|
||||||
public:
|
public:
|
||||||
NfcTag() {
|
NfcTag() { this->tag_type_ = "Unknown"; };
|
||||||
this->uid_ = {};
|
NfcTag(const NfcTagUid &uid) {
|
||||||
this->tag_type_ = "Unknown";
|
|
||||||
};
|
|
||||||
NfcTag(std::vector<uint8_t> &uid) {
|
|
||||||
this->uid_ = uid;
|
this->uid_ = uid;
|
||||||
this->tag_type_ = "Unknown";
|
this->tag_type_ = "Unknown";
|
||||||
};
|
};
|
||||||
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type) {
|
NfcTag(const NfcTagUid &uid, const std::string &tag_type) {
|
||||||
this->uid_ = uid;
|
this->uid_ = uid;
|
||||||
this->tag_type_ = tag_type;
|
this->tag_type_ = tag_type;
|
||||||
};
|
};
|
||||||
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type, std::unique_ptr<nfc::NdefMessage> ndef_message) {
|
NfcTag(const NfcTagUid &uid, const std::string &tag_type, std::unique_ptr<nfc::NdefMessage> ndef_message) {
|
||||||
this->uid_ = uid;
|
this->uid_ = uid;
|
||||||
this->tag_type_ = tag_type;
|
this->tag_type_ = tag_type;
|
||||||
this->ndef_message_ = std::move(ndef_message);
|
this->ndef_message_ = std::move(ndef_message);
|
||||||
};
|
};
|
||||||
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) {
|
NfcTag(const NfcTagUid &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) {
|
||||||
this->uid_ = uid;
|
this->uid_ = uid;
|
||||||
this->tag_type_ = tag_type;
|
this->tag_type_ = tag_type;
|
||||||
this->ndef_message_ = make_unique<NdefMessage>(ndef_data);
|
this->ndef_message_ = make_unique<NdefMessage>(ndef_data);
|
||||||
@@ -41,14 +42,14 @@ class NfcTag {
|
|||||||
ndef_message_ = make_unique<NdefMessage>(*rhs.ndef_message_);
|
ndef_message_ = make_unique<NdefMessage>(*rhs.ndef_message_);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> &get_uid() { return this->uid_; };
|
NfcTagUid &get_uid() { return this->uid_; };
|
||||||
const std::string &get_tag_type() { return this->tag_type_; };
|
const std::string &get_tag_type() { return this->tag_type_; };
|
||||||
bool has_ndef_message() { return this->ndef_message_ != nullptr; };
|
bool has_ndef_message() { return this->ndef_message_ != nullptr; };
|
||||||
const std::shared_ptr<NdefMessage> &get_ndef_message() { return this->ndef_message_; };
|
const std::shared_ptr<NdefMessage> &get_ndef_message() { return this->ndef_message_; };
|
||||||
void set_ndef_message(std::unique_ptr<NdefMessage> ndef_message) { this->ndef_message_ = std::move(ndef_message); };
|
void set_ndef_message(std::unique_ptr<NdefMessage> ndef_message) { this->ndef_message_ = std::move(ndef_message); };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<uint8_t> uid_;
|
NfcTagUid uid_;
|
||||||
std::string tag_type_;
|
std::string tag_type_;
|
||||||
std::shared_ptr<NdefMessage> ndef_message_;
|
std::shared_ptr<NdefMessage> ndef_message_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,9 +69,21 @@ def set_core_data(config: ConfigType) -> ConfigType:
|
|||||||
|
|
||||||
|
|
||||||
def set_framework(config: ConfigType) -> ConfigType:
|
def set_framework(config: ConfigType) -> ConfigType:
|
||||||
version = cv.Version.parse(cv.version_number(config[CONF_FRAMEWORK][CONF_VERSION]))
|
framework_ver = cv.Version.parse(
|
||||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = version
|
cv.version_number(config[CONF_FRAMEWORK][CONF_VERSION])
|
||||||
return config
|
)
|
||||||
|
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = framework_ver
|
||||||
|
if framework_ver < cv.Version(2, 9, 2):
|
||||||
|
return cv.require_framework_version(
|
||||||
|
nrf52_zephyr=cv.Version(2, 6, 1, "a"),
|
||||||
|
)(config)
|
||||||
|
if framework_ver < cv.Version(3, 2, 0):
|
||||||
|
return cv.require_framework_version(
|
||||||
|
nrf52_zephyr=cv.Version(2, 9, 2, "2"),
|
||||||
|
)(config)
|
||||||
|
return cv.require_framework_version(
|
||||||
|
nrf52_zephyr=cv.Version(3, 2, 0, "1"),
|
||||||
|
)(config)
|
||||||
|
|
||||||
|
|
||||||
BOOTLOADERS = [
|
BOOTLOADERS = [
|
||||||
@@ -140,7 +152,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_FRAMEWORK, default={CONF_VERSION: "2.6.1-7"}): cv.Schema(
|
cv.Optional(CONF_FRAMEWORK, default={CONF_VERSION: "2.6.1-a"}): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_VERSION): cv.string_strict,
|
cv.Required(CONF_VERSION): cv.string_strict,
|
||||||
}
|
}
|
||||||
@@ -181,13 +193,12 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
cg.add_platformio_option(CONF_FRAMEWORK, CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK])
|
cg.add_platformio_option(CONF_FRAMEWORK, CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK])
|
||||||
cg.add_platformio_option(
|
cg.add_platformio_option(
|
||||||
"platform",
|
"platform",
|
||||||
"https://github.com/tomaszduda23/platform-nordicnrf52/archive/refs/tags/v10.3.0-1.zip",
|
"https://github.com/tomaszduda23/platform-nordicnrf52/archive/refs/tags/v10.3.0-5.zip",
|
||||||
)
|
)
|
||||||
cg.add_platformio_option(
|
cg.add_platformio_option(
|
||||||
"platform_packages",
|
"platform_packages",
|
||||||
[
|
[
|
||||||
f"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v{CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}.zip",
|
f"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v{CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}.zip",
|
||||||
"platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.17.4-0.zip",
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import mqtt, web_server
|
from esphome.components import mqtt, web_server, zigbee
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ABOVE,
|
CONF_ABOVE,
|
||||||
@@ -189,6 +189,7 @@ validate_unit_of_measurement = cv.string_strict
|
|||||||
_NUMBER_SCHEMA = (
|
_NUMBER_SCHEMA = (
|
||||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
|
.extend(zigbee.NUMBER_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
|
||||||
@@ -214,6 +215,7 @@ _NUMBER_SCHEMA = (
|
|||||||
|
|
||||||
|
|
||||||
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
|
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
|
||||||
|
_NUMBER_SCHEMA.add_extra(zigbee.validate_number)
|
||||||
|
|
||||||
|
|
||||||
def number_schema(
|
def number_schema(
|
||||||
@@ -277,6 +279,8 @@ async def setup_number_core_(
|
|||||||
if web_server_config := config.get(CONF_WEB_SERVER):
|
if web_server_config := config.get(CONF_WEB_SERVER):
|
||||||
await web_server.add_entity_config(var, web_server_config)
|
await web_server.add_entity_config(var, web_server_config)
|
||||||
|
|
||||||
|
await zigbee.setup_number(var, config, min_value, max_value, step)
|
||||||
|
|
||||||
|
|
||||||
async def register_number(
|
async def register_number(
|
||||||
var, config, *, min_value: float, max_value: float, step: float
|
var, config, *, min_value: float, max_value: float, step: float
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ void ValueRangeTrigger::setup() {
|
|||||||
float local_min = this->min_.value(0.0);
|
float local_min = this->min_.value(0.0);
|
||||||
float local_max = this->max_.value(0.0);
|
float local_max = this->max_.value(0.0);
|
||||||
convert hash = {.from = (local_max - local_min)};
|
convert hash = {.from = (local_max - local_min)};
|
||||||
uint32_t myhash = hash.to ^ this->parent_->get_preference_hash();
|
this->rtc_ = this->parent_->make_entity_preference<bool>(hash.to);
|
||||||
this->rtc_ = global_preferences->make_preference<bool>(myhash);
|
|
||||||
bool initial_state;
|
bool initial_state;
|
||||||
if (this->rtc_.load(&initial_state)) {
|
if (this->rtc_.load(&initial_state)) {
|
||||||
this->previous_in_range_ = initial_state;
|
this->previous_in_range_ = initial_state;
|
||||||
|
|||||||
@@ -14,18 +14,9 @@ void log_number(const char *tag, const char *prefix, const char *type, Number *o
|
|||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||||
|
LOG_ENTITY_ICON(tag, prefix, *obj);
|
||||||
if (!obj->get_icon_ref().empty()) {
|
LOG_ENTITY_UNIT_OF_MEASUREMENT(tag, prefix, obj->traits);
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
LOG_ENTITY_DEVICE_CLASS(tag, prefix, obj->traits);
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj->traits.get_unit_of_measurement_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Unit of Measurement: '%s'", prefix, obj->traits.get_unit_of_measurement_ref().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj->traits.get_device_class_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->traits.get_device_class_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Number::publish_state(float state) {
|
void Number::publish_state(float state) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ void OpenthermNumber::setup() {
|
|||||||
if (!this->restore_value_) {
|
if (!this->restore_value_) {
|
||||||
value = this->initial_value_;
|
value = this->initial_value_;
|
||||||
} else {
|
} else {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<float>();
|
||||||
if (!this->pref_.load(&value)) {
|
if (!this->pref_.load(&value)) {
|
||||||
if (!std::isnan(this->initial_value_)) {
|
if (!std::isnan(this->initial_value_)) {
|
||||||
value = this->initial_value_;
|
value = this->initial_value_;
|
||||||
|
|||||||
@@ -168,11 +168,11 @@ void PN532::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint8_t nfcid_length = read[5];
|
uint8_t nfcid_length = read[5];
|
||||||
std::vector<uint8_t> nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
|
if (nfcid_length > nfc::NFC_UID_MAX_LENGTH || read.size() < 6U + nfcid_length) {
|
||||||
if (read.size() < 6U + nfcid_length) {
|
|
||||||
// oops, pn532 returned invalid data
|
// oops, pn532 returned invalid data
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
nfc::NfcTagUid nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
|
||||||
|
|
||||||
bool report = true;
|
bool report = true;
|
||||||
for (auto *bin_sens : this->binary_sensors_) {
|
for (auto *bin_sens : this->binary_sensors_) {
|
||||||
@@ -358,7 +358,7 @@ void PN532::turn_off_rf_() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> PN532::read_tag_(std::vector<uint8_t> &uid) {
|
std::unique_ptr<nfc::NfcTag> PN532::read_tag_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
|
|
||||||
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
||||||
@@ -393,7 +393,7 @@ void PN532::write_mode(nfc::NdefMessage *message) {
|
|||||||
ESP_LOGD(TAG, "Waiting to write next tag");
|
ESP_LOGD(TAG, "Waiting to write next tag");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::clean_tag_(std::vector<uint8_t> &uid) {
|
bool PN532::clean_tag_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
||||||
return this->format_mifare_classic_mifare_(uid);
|
return this->format_mifare_classic_mifare_(uid);
|
||||||
@@ -404,7 +404,7 @@ bool PN532::clean_tag_(std::vector<uint8_t> &uid) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::format_tag_(std::vector<uint8_t> &uid) {
|
bool PN532::format_tag_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
||||||
return this->format_mifare_classic_ndef_(uid);
|
return this->format_mifare_classic_ndef_(uid);
|
||||||
@@ -415,7 +415,7 @@ bool PN532::format_tag_(std::vector<uint8_t> &uid) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
|
bool PN532::write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
|
||||||
return this->write_mifare_classic_tag_(uid, message);
|
return this->write_mifare_classic_tag_(uid, message);
|
||||||
@@ -448,7 +448,7 @@ void PN532::dump_config() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
|
bool PN532BinarySensor::process(const nfc::NfcTagUid &data) {
|
||||||
if (data.size() != this->uid_.size())
|
if (data.size() != this->uid_.size())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -69,28 +69,28 @@ class PN532 : public PollingComponent {
|
|||||||
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
|
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
|
||||||
virtual bool read_response(uint8_t command, std::vector<uint8_t> &data) = 0;
|
virtual bool read_response(uint8_t command, std::vector<uint8_t> &data) = 0;
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> read_tag_(std::vector<uint8_t> &uid);
|
std::unique_ptr<nfc::NfcTag> read_tag_(nfc::NfcTagUid &uid);
|
||||||
|
|
||||||
bool format_tag_(std::vector<uint8_t> &uid);
|
bool format_tag_(nfc::NfcTagUid &uid);
|
||||||
bool clean_tag_(std::vector<uint8_t> &uid);
|
bool clean_tag_(nfc::NfcTagUid &uid);
|
||||||
bool write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
bool write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(std::vector<uint8_t> &uid);
|
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(nfc::NfcTagUid &uid);
|
||||||
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
|
||||||
bool auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key);
|
||||||
bool format_mifare_classic_mifare_(std::vector<uint8_t> &uid);
|
bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid);
|
||||||
bool format_mifare_classic_ndef_(std::vector<uint8_t> &uid);
|
bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid);
|
||||||
bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
bool write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid);
|
std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(nfc::NfcTagUid &uid);
|
||||||
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
|
||||||
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
|
||||||
uint16_t read_mifare_ultralight_capacity_();
|
uint16_t read_mifare_ultralight_capacity_();
|
||||||
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
|
||||||
bool clean_mifare_ultralight_();
|
bool clean_mifare_ultralight_();
|
||||||
|
|
||||||
bool updates_enabled_{true};
|
bool updates_enabled_{true};
|
||||||
@@ -98,7 +98,7 @@ class PN532 : public PollingComponent {
|
|||||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||||
std::vector<uint8_t> current_uid_;
|
nfc::NfcTagUid current_uid_;
|
||||||
nfc::NdefMessage *next_task_message_to_write_;
|
nfc::NdefMessage *next_task_message_to_write_;
|
||||||
uint32_t rd_start_time_{0};
|
uint32_t rd_start_time_{0};
|
||||||
enum PN532ReadReady rd_ready_ { WOULDBLOCK };
|
enum PN532ReadReady rd_ready_ { WOULDBLOCK };
|
||||||
@@ -118,9 +118,9 @@ class PN532 : public PollingComponent {
|
|||||||
|
|
||||||
class PN532BinarySensor : public binary_sensor::BinarySensor {
|
class PN532BinarySensor : public binary_sensor::BinarySensor {
|
||||||
public:
|
public:
|
||||||
void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; }
|
void set_uid(const nfc::NfcTagUid &uid) { uid_ = uid; }
|
||||||
|
|
||||||
bool process(std::vector<uint8_t> &data);
|
bool process(const nfc::NfcTagUid &data);
|
||||||
|
|
||||||
void on_scan_end() {
|
void on_scan_end() {
|
||||||
if (!this->found_) {
|
if (!this->found_) {
|
||||||
@@ -130,7 +130,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<uint8_t> uid_;
|
nfc::NfcTagUid uid_;
|
||||||
bool found_{false};
|
bool found_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace pn532 {
|
|||||||
|
|
||||||
static const char *const TAG = "pn532.mifare_classic";
|
static const char *const TAG = "pn532.mifare_classic";
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(std::vector<uint8_t> &uid) {
|
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t current_block = 4;
|
uint8_t current_block = 4;
|
||||||
uint8_t message_start_index = 0;
|
uint8_t message_start_index = 0;
|
||||||
uint32_t message_length = 0;
|
uint32_t message_length = 0;
|
||||||
@@ -82,8 +82,7 @@ bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num,
|
bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key) {
|
||||||
const uint8_t *key) {
|
|
||||||
std::vector<uint8_t> data({
|
std::vector<uint8_t> data({
|
||||||
PN532_COMMAND_INDATAEXCHANGE,
|
PN532_COMMAND_INDATAEXCHANGE,
|
||||||
0x01, // One card
|
0x01, // One card
|
||||||
@@ -106,7 +105,7 @@ bool PN532::auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::format_mifare_classic_mifare_(std::vector<uint8_t> &uid) {
|
bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
|
||||||
std::vector<uint8_t> blank_buffer(
|
std::vector<uint8_t> blank_buffer(
|
||||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
std::vector<uint8_t> trailer_buffer(
|
std::vector<uint8_t> trailer_buffer(
|
||||||
@@ -141,7 +140,7 @@ bool PN532::format_mifare_classic_mifare_(std::vector<uint8_t> &uid) {
|
|||||||
return !error;
|
return !error;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::format_mifare_classic_ndef_(std::vector<uint8_t> &uid) {
|
bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
|
||||||
std::vector<uint8_t> empty_ndef_message(
|
std::vector<uint8_t> empty_ndef_message(
|
||||||
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
std::vector<uint8_t> blank_block(
|
std::vector<uint8_t> blank_block(
|
||||||
@@ -216,7 +215,7 @@ bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
|
bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
|
||||||
auto encoded = message->encode();
|
auto encoded = message->encode();
|
||||||
|
|
||||||
uint32_t message_length = encoded.size();
|
uint32_t message_length = encoded.size();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace pn532 {
|
|||||||
|
|
||||||
static const char *const TAG = "pn532.mifare_ultralight";
|
static const char *const TAG = "pn532.mifare_ultralight";
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) {
|
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) {
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all
|
||||||
if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
|
||||||
@@ -114,7 +114,7 @@ bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PN532::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) {
|
bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
|
||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
|
|
||||||
auto encoded = message->encode();
|
auto encoded = message->encode();
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ uint8_t PN7150::read_endpoint_data_(nfc::NfcTag &tag) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::clean_endpoint_(std::vector<uint8_t> &uid) {
|
uint8_t PN7150::clean_endpoint_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -494,7 +494,7 @@ uint8_t PN7150::clean_endpoint_(std::vector<uint8_t> &uid) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::format_endpoint_(std::vector<uint8_t> &uid) {
|
uint8_t PN7150::format_endpoint_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -510,7 +510,7 @@ uint8_t PN7150::format_endpoint_(std::vector<uint8_t> &uid) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message) {
|
uint8_t PN7150::write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -534,7 +534,7 @@ std::unique_ptr<nfc::NfcTag> PN7150::build_tag_(const uint8_t mode_tech, const s
|
|||||||
ESP_LOGE(TAG, "UID length cannot be zero");
|
ESP_LOGE(TAG, "UID length cannot be zero");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> uid(data.begin() + 3, data.begin() + 3 + uid_length);
|
nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length);
|
||||||
const auto *tag_type_str =
|
const auto *tag_type_str =
|
||||||
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
|
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
|
||||||
return make_unique<nfc::NfcTag>(uid, tag_type_str);
|
return make_unique<nfc::NfcTag>(uid, tag_type_str);
|
||||||
@@ -543,7 +543,7 @@ std::unique_ptr<nfc::NfcTag> PN7150::build_tag_(const uint8_t mode_tech, const s
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<size_t> PN7150::find_tag_uid_(const std::vector<uint8_t> &uid) {
|
optional<size_t> PN7150::find_tag_uid_(const nfc::NfcTagUid &uid) {
|
||||||
if (!this->discovered_endpoint_.empty()) {
|
if (!this->discovered_endpoint_.empty()) {
|
||||||
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
||||||
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();
|
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();
|
||||||
|
|||||||
@@ -203,12 +203,12 @@ class PN7150 : public nfc::Nfcc, public Component {
|
|||||||
void select_endpoint_();
|
void select_endpoint_();
|
||||||
|
|
||||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||||
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
|
||||||
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
uint8_t format_endpoint_(nfc::NfcTagUid &uid);
|
||||||
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||||
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
optional<size_t> find_tag_uid_(const nfc::NfcTagUid &uid);
|
||||||
void purge_old_tags_();
|
void purge_old_tags_();
|
||||||
void erase_tag_(uint8_t tag_index);
|
void erase_tag_(uint8_t tag_index);
|
||||||
|
|
||||||
@@ -251,7 +251,7 @@ class PN7150 : public nfc::Nfcc, public Component {
|
|||||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
uint8_t clean_mifare_ultralight_();
|
uint8_t clean_mifare_ultralight_();
|
||||||
|
|
||||||
enum NfcTask : uint8_t {
|
enum NfcTask : uint8_t {
|
||||||
|
|||||||
@@ -115,8 +115,7 @@ uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7150::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid,
|
uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
const std::shared_ptr<nfc::NdefMessage> &message) {
|
|
||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
|
|
||||||
auto encoded = message->encode();
|
auto encoded = message->encode();
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ uint8_t PN7160::read_endpoint_data_(nfc::NfcTag &tag) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::clean_endpoint_(std::vector<uint8_t> &uid) {
|
uint8_t PN7160::clean_endpoint_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -522,7 +522,7 @@ uint8_t PN7160::clean_endpoint_(std::vector<uint8_t> &uid) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::format_endpoint_(std::vector<uint8_t> &uid) {
|
uint8_t PN7160::format_endpoint_(nfc::NfcTagUid &uid) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -538,7 +538,7 @@ uint8_t PN7160::format_endpoint_(std::vector<uint8_t> &uid) {
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message) {
|
uint8_t PN7160::write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
uint8_t type = nfc::guess_tag_type(uid.size());
|
uint8_t type = nfc::guess_tag_type(uid.size());
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
case nfc::TAG_TYPE_MIFARE_CLASSIC:
|
||||||
@@ -562,7 +562,7 @@ std::unique_ptr<nfc::NfcTag> PN7160::build_tag_(const uint8_t mode_tech, const s
|
|||||||
ESP_LOGE(TAG, "UID length cannot be zero");
|
ESP_LOGE(TAG, "UID length cannot be zero");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> uid(data.begin() + 3, data.begin() + 3 + uid_length);
|
nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length);
|
||||||
const auto *tag_type_str =
|
const auto *tag_type_str =
|
||||||
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
|
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
|
||||||
return make_unique<nfc::NfcTag>(uid, tag_type_str);
|
return make_unique<nfc::NfcTag>(uid, tag_type_str);
|
||||||
@@ -571,7 +571,7 @@ std::unique_ptr<nfc::NfcTag> PN7160::build_tag_(const uint8_t mode_tech, const s
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<size_t> PN7160::find_tag_uid_(const std::vector<uint8_t> &uid) {
|
optional<size_t> PN7160::find_tag_uid_(const nfc::NfcTagUid &uid) {
|
||||||
if (!this->discovered_endpoint_.empty()) {
|
if (!this->discovered_endpoint_.empty()) {
|
||||||
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
|
||||||
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();
|
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();
|
||||||
|
|||||||
@@ -220,12 +220,12 @@ class PN7160 : public nfc::Nfcc, public Component {
|
|||||||
void select_endpoint_();
|
void select_endpoint_();
|
||||||
|
|
||||||
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
uint8_t read_endpoint_data_(nfc::NfcTag &tag);
|
||||||
uint8_t clean_endpoint_(std::vector<uint8_t> &uid);
|
uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
|
||||||
uint8_t format_endpoint_(std::vector<uint8_t> &uid);
|
uint8_t format_endpoint_(nfc::NfcTagUid &uid);
|
||||||
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
|
|
||||||
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
|
||||||
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid);
|
optional<size_t> find_tag_uid_(const nfc::NfcTagUid &uid);
|
||||||
void purge_old_tags_();
|
void purge_old_tags_();
|
||||||
void erase_tag_(uint8_t tag_index);
|
void erase_tag_(uint8_t tag_index);
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ class PN7160 : public nfc::Nfcc, public Component {
|
|||||||
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
|
||||||
uint8_t &message_start_index);
|
uint8_t &message_start_index);
|
||||||
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
|
||||||
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
|
||||||
uint8_t clean_mifare_ultralight_();
|
uint8_t clean_mifare_ultralight_();
|
||||||
|
|
||||||
enum NfcTask : uint8_t {
|
enum NfcTask : uint8_t {
|
||||||
|
|||||||
@@ -115,8 +115,7 @@ uint8_t PN7160::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
|
|||||||
return nfc::STATUS_FAILED;
|
return nfc::STATUS_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t PN7160::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid,
|
uint8_t PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
|
||||||
const std::shared_ptr<nfc::NdefMessage> &message) {
|
|
||||||
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
uint32_t capacity = this->read_mifare_ultralight_capacity_();
|
||||||
|
|
||||||
auto encoded = message->encode();
|
auto encoded = message->encode();
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
|||||||
void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); }
|
void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); }
|
||||||
|
|
||||||
bool canHandle(AsyncWebServerRequest *request) const override {
|
bool canHandle(AsyncWebServerRequest *request) const override {
|
||||||
if (request->method() == HTTP_GET) {
|
if (request->method() != HTTP_GET)
|
||||||
if (request->url() == "/metrics")
|
return false;
|
||||||
return true;
|
#ifdef USE_ESP32
|
||||||
}
|
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
|
||||||
|
return request->url_to(url_buf) == "/metrics";
|
||||||
return false;
|
#else
|
||||||
|
return request->url() == ESPHOME_F("/metrics");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleRequest(AsyncWebServerRequest *req) override;
|
void handleRequest(AsyncWebServerRequest *req) override;
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
|
||||||
|
esp32.include_builtin_idf_component("esp_driver_rmt")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||||
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
||||||
cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS]))
|
cg.add(var.set_receive_symbols(config[CONF_RECEIVE_SYMBOLS]))
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ async def digital_write_action_to_code(config, action_id, template_arg, args):
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
pin = await cg.gpio_pin_expression(config[CONF_PIN])
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
# Re-enable ESP-IDF's RMT driver (excluded by default to save compile time)
|
||||||
|
esp32.include_builtin_idf_component("esp_driver_rmt")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID], pin)
|
var = cg.new_Pvariable(config[CONF_ID], pin)
|
||||||
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
|
||||||
cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING]))
|
cg.add(var.set_non_blocking(config[CONF_NON_BLOCKING]))
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ void RotaryEncoderSensor::setup() {
|
|||||||
int32_t initial_value = 0;
|
int32_t initial_value = 0;
|
||||||
switch (this->restore_mode_) {
|
switch (this->restore_mode_) {
|
||||||
case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
|
case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
|
||||||
this->rtc_ = global_preferences->make_preference<int32_t>(this->get_preference_hash());
|
this->rtc_ = this->make_entity_preference<int32_t>();
|
||||||
if (!this->rtc_.load(&initial_value)) {
|
if (!this->rtc_.load(&initial_value)) {
|
||||||
initial_value = 0;
|
initial_value = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
@@ -25,6 +24,9 @@ static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-no
|
|||||||
|
|
||||||
static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
|
static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
|
||||||
|
|
||||||
|
// Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation
|
||||||
|
static constexpr size_t PREF_BUFFER_SIZE = 64;
|
||||||
|
|
||||||
extern "C" uint8_t _EEPROM_start;
|
extern "C" uint8_t _EEPROM_start;
|
||||||
|
|
||||||
template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) {
|
template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) {
|
||||||
@@ -42,12 +44,14 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend {
|
|||||||
uint32_t type = 0;
|
uint32_t type = 0;
|
||||||
|
|
||||||
bool save(const uint8_t *data, size_t len) override {
|
bool save(const uint8_t *data, size_t len) override {
|
||||||
std::vector<uint8_t> buffer;
|
const size_t buffer_size = len + 1;
|
||||||
buffer.resize(len + 1);
|
SmallBufferWithHeapFallback<PREF_BUFFER_SIZE> buffer_alloc(buffer_size);
|
||||||
memcpy(buffer.data(), data, len);
|
uint8_t *buffer = buffer_alloc.get();
|
||||||
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len + 1; i++) {
|
memcpy(buffer, data, len);
|
||||||
|
buffer[len] = calculate_crc(buffer, buffer + len, type);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < buffer_size; i++) {
|
||||||
uint32_t j = offset + i;
|
uint32_t j = offset + i;
|
||||||
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
||||||
return false;
|
return false;
|
||||||
@@ -60,22 +64,23 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool load(uint8_t *data, size_t len) override {
|
bool load(uint8_t *data, size_t len) override {
|
||||||
std::vector<uint8_t> buffer;
|
const size_t buffer_size = len + 1;
|
||||||
buffer.resize(len + 1);
|
SmallBufferWithHeapFallback<PREF_BUFFER_SIZE> buffer_alloc(buffer_size);
|
||||||
|
uint8_t *buffer = buffer_alloc.get();
|
||||||
|
|
||||||
for (size_t i = 0; i < len + 1; i++) {
|
for (size_t i = 0; i < buffer_size; i++) {
|
||||||
uint32_t j = offset + i;
|
uint32_t j = offset + i;
|
||||||
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
if (j >= RP2040_FLASH_STORAGE_SIZE)
|
||||||
return false;
|
return false;
|
||||||
buffer[i] = s_flash_storage[j];
|
buffer[i] = s_flash_storage[j];
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type);
|
uint8_t crc = calculate_crc(buffer, buffer + len, type);
|
||||||
if (buffer[buffer.size() - 1] != crc) {
|
if (buffer[len] != crc) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(data, buffer.data(), len);
|
memcpy(data, buffer, len);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ RTL87XX_BOARDS = {
|
|||||||
"name": "WR2 Wi-Fi Module",
|
"name": "WR2 Wi-Fi Module",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
},
|
},
|
||||||
|
"wbr3": {
|
||||||
|
"name": "WBR3 Wi-Fi Module",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
"generic-rtl8710bn-2mb-468k": {
|
"generic-rtl8710bn-2mb-468k": {
|
||||||
"name": "Generic - RTL8710BN (2M/468k)",
|
"name": "Generic - RTL8710BN (2M/468k)",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
@@ -79,6 +83,10 @@ RTL87XX_BOARDS = {
|
|||||||
"name": "T103_V1.0",
|
"name": "T103_V1.0",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
},
|
},
|
||||||
|
"generic-rtl8720cf-2mb-896k": {
|
||||||
|
"name": "Generic - RTL8720CF (2M/896k)",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
"generic-rtl8720cf-2mb-992k": {
|
"generic-rtl8720cf-2mb-992k": {
|
||||||
"name": "Generic - RTL8720CF (2M/992k)",
|
"name": "Generic - RTL8720CF (2M/992k)",
|
||||||
"family": FAMILY_RTL8720C,
|
"family": FAMILY_RTL8720C,
|
||||||
@@ -221,6 +229,71 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"D9": 29,
|
"D9": 29,
|
||||||
"A1": 41,
|
"A1": 41,
|
||||||
},
|
},
|
||||||
|
"wbr3": {
|
||||||
|
"WIRE0_SCL_0": 11,
|
||||||
|
"WIRE0_SCL_1": 2,
|
||||||
|
"WIRE0_SCL_2": 19,
|
||||||
|
"WIRE0_SCL_3": 15,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 12,
|
||||||
|
"WIRE0_SDA_2": 16,
|
||||||
|
"SERIAL0_RX_0": 12,
|
||||||
|
"SERIAL0_RX_1": 13,
|
||||||
|
"SERIAL0_TX_0": 11,
|
||||||
|
"SERIAL0_TX_1": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX_0": 2,
|
||||||
|
"SERIAL1_RX_1": 0,
|
||||||
|
"SERIAL1_TX_0": 3,
|
||||||
|
"SERIAL1_TX_1": 1,
|
||||||
|
"SERIAL2_CTS": 19,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CS0": 15,
|
||||||
|
"CTS1": 4,
|
||||||
|
"CTS2": 19,
|
||||||
|
"PA00": 0,
|
||||||
|
"PA0": 0,
|
||||||
|
"PA01": 1,
|
||||||
|
"PA1": 1,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA07": 7,
|
||||||
|
"PA7": 7,
|
||||||
|
"PA11": 11,
|
||||||
|
"PA12": 12,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PA19": 19,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"RX2": 15,
|
||||||
|
"SDA0": 16,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 7,
|
||||||
|
"D1": 11,
|
||||||
|
"D2": 2,
|
||||||
|
"D3": 3,
|
||||||
|
"D4": 4,
|
||||||
|
"D5": 12,
|
||||||
|
"D6": 16,
|
||||||
|
"D7": 17,
|
||||||
|
"D8": 18,
|
||||||
|
"D9": 19,
|
||||||
|
"D10": 13,
|
||||||
|
"D11": 14,
|
||||||
|
"D12": 15,
|
||||||
|
"D13": 0,
|
||||||
|
"D14": 1,
|
||||||
|
},
|
||||||
"generic-rtl8710bn-2mb-468k": {
|
"generic-rtl8710bn-2mb-468k": {
|
||||||
"SPI0_CS": 19,
|
"SPI0_CS": 19,
|
||||||
"SPI0_MISO": 22,
|
"SPI0_MISO": 22,
|
||||||
@@ -1178,6 +1251,104 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"A0": 19,
|
"A0": 19,
|
||||||
"A1": 41,
|
"A1": 41,
|
||||||
},
|
},
|
||||||
|
"generic-rtl8720cf-2mb-896k": {
|
||||||
|
"SPI0_CS_0": 2,
|
||||||
|
"SPI0_CS_1": 7,
|
||||||
|
"SPI0_CS_2": 15,
|
||||||
|
"SPI0_MISO_0": 10,
|
||||||
|
"SPI0_MISO_1": 20,
|
||||||
|
"SPI0_MOSI_0": 4,
|
||||||
|
"SPI0_MOSI_1": 9,
|
||||||
|
"SPI0_MOSI_2": 19,
|
||||||
|
"SPI0_SCK_0": 3,
|
||||||
|
"SPI0_SCK_1": 8,
|
||||||
|
"SPI0_SCK_2": 16,
|
||||||
|
"WIRE0_SCL_0": 2,
|
||||||
|
"WIRE0_SCL_1": 11,
|
||||||
|
"WIRE0_SCL_2": 15,
|
||||||
|
"WIRE0_SCL_3": 19,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 12,
|
||||||
|
"WIRE0_SDA_2": 16,
|
||||||
|
"WIRE0_SDA_3": 20,
|
||||||
|
"SERIAL0_CTS": 10,
|
||||||
|
"SERIAL0_RTS": 9,
|
||||||
|
"SERIAL0_RX_0": 12,
|
||||||
|
"SERIAL0_RX_1": 13,
|
||||||
|
"SERIAL0_TX_0": 11,
|
||||||
|
"SERIAL0_TX_1": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX_0": 0,
|
||||||
|
"SERIAL1_RX_1": 2,
|
||||||
|
"SERIAL1_TX_0": 1,
|
||||||
|
"SERIAL1_TX_1": 3,
|
||||||
|
"SERIAL2_CTS": 19,
|
||||||
|
"SERIAL2_RTS": 20,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CS0": 15,
|
||||||
|
"CTS0": 10,
|
||||||
|
"CTS1": 4,
|
||||||
|
"CTS2": 19,
|
||||||
|
"MOSI0": 19,
|
||||||
|
"PA00": 0,
|
||||||
|
"PA0": 0,
|
||||||
|
"PA01": 1,
|
||||||
|
"PA1": 1,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA07": 7,
|
||||||
|
"PA7": 7,
|
||||||
|
"PA08": 8,
|
||||||
|
"PA8": 8,
|
||||||
|
"PA09": 9,
|
||||||
|
"PA9": 9,
|
||||||
|
"PA10": 10,
|
||||||
|
"PA11": 11,
|
||||||
|
"PA12": 12,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PA19": 19,
|
||||||
|
"PA20": 20,
|
||||||
|
"PA23": 23,
|
||||||
|
"PWM0": 20,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"PWM7": 23,
|
||||||
|
"RTS0": 9,
|
||||||
|
"RTS2": 20,
|
||||||
|
"RX2": 15,
|
||||||
|
"SCK0": 16,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 0,
|
||||||
|
"D1": 1,
|
||||||
|
"D2": 2,
|
||||||
|
"D3": 3,
|
||||||
|
"D4": 4,
|
||||||
|
"D5": 7,
|
||||||
|
"D6": 8,
|
||||||
|
"D7": 9,
|
||||||
|
"D8": 10,
|
||||||
|
"D9": 11,
|
||||||
|
"D10": 12,
|
||||||
|
"D11": 13,
|
||||||
|
"D12": 14,
|
||||||
|
"D13": 15,
|
||||||
|
"D14": 16,
|
||||||
|
"D15": 17,
|
||||||
|
"D16": 18,
|
||||||
|
"D17": 19,
|
||||||
|
"D18": 20,
|
||||||
|
"D19": 23,
|
||||||
|
},
|
||||||
"generic-rtl8720cf-2mb-992k": {
|
"generic-rtl8720cf-2mb-992k": {
|
||||||
"SPI0_CS_0": 2,
|
"SPI0_CS_0": 2,
|
||||||
"SPI0_CS_1": 7,
|
"SPI0_CS_1": 7,
|
||||||
|
|||||||
@@ -27,46 +27,61 @@ void RuntimeStatsCollector::record_component_time(Component *component, uint32_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RuntimeStatsCollector::log_stats_() {
|
void RuntimeStatsCollector::log_stats_() {
|
||||||
|
// First pass: count active components
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto &it : this->component_stats_) {
|
||||||
|
if (it.second.get_period_count() > 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ESP_LOGI(TAG,
|
ESP_LOGI(TAG,
|
||||||
"Component Runtime Statistics\n"
|
"Component Runtime Statistics\n"
|
||||||
" Period stats (last %" PRIu32 "ms):",
|
" Period stats (last %" PRIu32 "ms): %zu active components",
|
||||||
this->log_interval_);
|
this->log_interval_, count);
|
||||||
|
|
||||||
// First collect stats we want to display
|
if (count == 0) {
|
||||||
std::vector<ComponentStatPair> stats_to_display;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack buffer sized to actual active count (up to 256 components), heap fallback for larger
|
||||||
|
SmallBufferWithHeapFallback<256, Component *> buffer(count);
|
||||||
|
Component **sorted = buffer.get();
|
||||||
|
|
||||||
|
// Second pass: fill buffer with active components
|
||||||
|
size_t idx = 0;
|
||||||
for (const auto &it : this->component_stats_) {
|
for (const auto &it : this->component_stats_) {
|
||||||
Component *component = it.first;
|
if (it.second.get_period_count() > 0) {
|
||||||
const ComponentRuntimeStats &stats = it.second;
|
sorted[idx++] = it.first;
|
||||||
if (stats.get_period_count() > 0) {
|
|
||||||
ComponentStatPair pair = {component, &stats};
|
|
||||||
stats_to_display.push_back(pair);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by period runtime (descending)
|
// Sort by period runtime (descending)
|
||||||
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
|
||||||
|
return this->component_stats_[a].get_period_time_ms() > this->component_stats_[b].get_period_time_ms();
|
||||||
|
});
|
||||||
|
|
||||||
// Log top components by period runtime
|
// Log top components by period runtime
|
||||||
for (const auto &it : stats_to_display) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
const auto &stats = this->component_stats_[sorted[i]];
|
||||||
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||||
LOG_STR_ARG(it.component->get_component_log_str()), it.stats->get_period_count(),
|
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_period_count(), stats.get_period_avg_time_ms(),
|
||||||
it.stats->get_period_avg_time_ms(), it.stats->get_period_max_time_ms(), it.stats->get_period_time_ms());
|
stats.get_period_max_time_ms(), stats.get_period_time_ms());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log total stats since boot
|
// Log total stats since boot (only for active components - idle ones haven't changed)
|
||||||
ESP_LOGI(TAG, " Total stats (since boot):");
|
ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count);
|
||||||
|
|
||||||
// Re-sort by total runtime for all-time stats
|
// Re-sort by total runtime for all-time stats
|
||||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
|
||||||
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
return this->component_stats_[a].get_total_time_ms() > this->component_stats_[b].get_total_time_ms();
|
||||||
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
});
|
||||||
});
|
|
||||||
|
|
||||||
for (const auto &it : stats_to_display) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
const auto &stats = this->component_stats_[sorted[i]];
|
||||||
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||||
LOG_STR_ARG(it.component->get_component_log_str()), it.stats->get_total_count(),
|
LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_total_count(), stats.get_total_avg_time_ms(),
|
||||||
it.stats->get_total_avg_time_ms(), it.stats->get_total_max_time_ms(), it.stats->get_total_time_ms());
|
stats.get_total_max_time_ms(), stats.get_total_time_ms());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#ifdef USE_RUNTIME_STATS
|
#ifdef USE_RUNTIME_STATS
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
@@ -77,17 +76,6 @@ class ComponentRuntimeStats {
|
|||||||
uint32_t total_max_time_ms_;
|
uint32_t total_max_time_ms_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For sorting components by run time
|
|
||||||
struct ComponentStatPair {
|
|
||||||
Component *component;
|
|
||||||
const ComponentRuntimeStats *stats;
|
|
||||||
|
|
||||||
bool operator>(const ComponentStatPair &other) const {
|
|
||||||
// Sort by period time as that's what we're displaying in the logs
|
|
||||||
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class RuntimeStatsCollector {
|
class RuntimeStatsCollector {
|
||||||
public:
|
public:
|
||||||
RuntimeStatsCollector();
|
RuntimeStatsCollector();
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ namespace esphome::select {
|
|||||||
#define LOG_SELECT(prefix, type, obj) \
|
#define LOG_SELECT(prefix, type, obj) \
|
||||||
if ((obj) != nullptr) { \
|
if ((obj) != nullptr) { \
|
||||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||||
if (!(obj)->get_icon_ref().empty()) { \
|
LOG_ENTITY_ICON(TAG, prefix, *(obj)); \
|
||||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
|
|
||||||
} \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SUB_SELECT(name) \
|
#define SUB_SELECT(name) \
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ValueRangeTrigger : public Trigger<float>, public Component {
|
|||||||
template<typename V> void set_max(V max) { this->max_ = max; }
|
template<typename V> void set_max(V max) { this->max_ = max; }
|
||||||
|
|
||||||
void setup() override {
|
void setup() override {
|
||||||
this->rtc_ = global_preferences->make_preference<bool>(this->parent_->get_preference_hash());
|
this->rtc_ = this->parent_->make_entity_preference<bool>();
|
||||||
bool initial_state;
|
bool initial_state;
|
||||||
if (this->rtc_.load(&initial_state)) {
|
if (this->rtc_.load(&initial_state)) {
|
||||||
this->previous_in_range_ = initial_state;
|
this->previous_in_range_ = initial_state;
|
||||||
|
|||||||
@@ -22,13 +22,8 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o
|
|||||||
LOG_STR_ARG(state_class_to_string(obj->get_state_class())), prefix,
|
LOG_STR_ARG(state_class_to_string(obj->get_state_class())), prefix,
|
||||||
obj->get_unit_of_measurement_ref().c_str(), prefix, obj->get_accuracy_decimals());
|
obj->get_unit_of_measurement_ref().c_str(), prefix, obj->get_accuracy_decimals());
|
||||||
|
|
||||||
if (!obj->get_device_class_ref().empty()) {
|
LOG_ENTITY_DEVICE_CLASS(tag, prefix, *obj);
|
||||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
LOG_ENTITY_ICON(tag, prefix, *obj);
|
||||||
}
|
|
||||||
|
|
||||||
if (!obj->get_icon_ref().empty()) {
|
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj->get_force_update()) {
|
if (obj->get_force_update()) {
|
||||||
ESP_LOGV(tag, "%s Force Update: YES", prefix);
|
ESP_LOGV(tag, "%s Force Update: YES", prefix);
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ void socket_delay(uint32_t ms) {
|
|||||||
// Use esp_delay with a callback that checks if socket data arrived.
|
// Use esp_delay with a callback that checks if socket data arrived.
|
||||||
// This allows the delay to exit early when socket_wake() is called by
|
// This allows the delay to exit early when socket_wake() is called by
|
||||||
// lwip recv_fn/accept_fn callbacks, reducing socket latency.
|
// lwip recv_fn/accept_fn callbacks, reducing socket latency.
|
||||||
|
//
|
||||||
|
// When ms is 0, we must use delay(0) because esp_delay(0, callback)
|
||||||
|
// exits immediately without yielding, which can cause watchdog timeouts
|
||||||
|
// when the main loop runs in high-frequency mode (e.g., during light effects).
|
||||||
|
if (ms == 0) {
|
||||||
|
delay(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
s_socket_woke = false;
|
s_socket_woke = false;
|
||||||
esp_delay(ms, []() { return !s_socket_woke; });
|
esp_delay(ms, []() { return !s_socket_woke; });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from esphome import automation
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import audio, audio_dac
|
from esphome.components import audio, audio_dac
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_DATA, CONF_ID, CONF_VOLUME
|
from esphome.const import CONF_AUDIO_DAC, CONF_DATA, CONF_ID, CONF_VOLUME
|
||||||
from esphome.core import CORE, ID
|
from esphome.core import CORE, ID
|
||||||
from esphome.coroutine import CoroPriority, coroutine_with_priority
|
from esphome.coroutine import CoroPriority, coroutine_with_priority
|
||||||
|
|
||||||
@@ -11,8 +11,6 @@ CODEOWNERS = ["@jesserockz", "@kahrendt"]
|
|||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
CONF_AUDIO_DAC = "audio_dac"
|
|
||||||
|
|
||||||
speaker_ns = cg.esphome_ns.namespace("speaker")
|
speaker_ns = cg.esphome_ns.namespace("speaker")
|
||||||
|
|
||||||
Speaker = speaker_ns.class_("Speaker")
|
Speaker = speaker_ns.class_("Speaker")
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ void SpeakerMediaPlayer::setup() {
|
|||||||
|
|
||||||
this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand));
|
this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand));
|
||||||
|
|
||||||
this->pref_ = global_preferences->make_preference<VolumeRestoreState>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<VolumeRestoreState>();
|
||||||
|
|
||||||
VolumeRestoreState volume_restore_state;
|
VolumeRestoreState volume_restore_state;
|
||||||
if (this->pref_.load(&volume_restore_state)) {
|
if (this->pref_.load(&volume_restore_state)) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ void SprinklerControllerNumber::setup() {
|
|||||||
if (!this->restore_value_) {
|
if (!this->restore_value_) {
|
||||||
value = this->initial_value_;
|
value = this->initial_value_;
|
||||||
} else {
|
} else {
|
||||||
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<float>();
|
||||||
if (!this->pref_.load(&value)) {
|
if (!this->pref_.load(&value)) {
|
||||||
if (!std::isnan(this->initial_value_)) {
|
if (!std::isnan(this->initial_value_)) {
|
||||||
value = this->initial_value_;
|
value = this->initial_value_;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ optional<bool> Switch::get_initial_state() {
|
|||||||
if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK))
|
if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
this->rtc_ = global_preferences->make_preference<bool>(this->get_preference_hash());
|
this->rtc_ = this->make_entity_preference<bool>();
|
||||||
bool initial_state;
|
bool initial_state;
|
||||||
if (!this->rtc_.load(&initial_state))
|
if (!this->rtc_.load(&initial_state))
|
||||||
return {};
|
return {};
|
||||||
@@ -96,18 +96,14 @@ void log_switch(const char *tag, const char *prefix, const char *type, Switch *o
|
|||||||
LOG_STR_ARG(onoff));
|
LOG_STR_ARG(onoff));
|
||||||
|
|
||||||
// Add optional fields separately
|
// Add optional fields separately
|
||||||
if (!obj->get_icon_ref().empty()) {
|
LOG_ENTITY_ICON(tag, prefix, *obj);
|
||||||
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
|
|
||||||
}
|
|
||||||
if (obj->assumed_state()) {
|
if (obj->assumed_state()) {
|
||||||
ESP_LOGCONFIG(tag, "%s Assumed State: YES", prefix);
|
ESP_LOGCONFIG(tag, "%s Assumed State: YES", prefix);
|
||||||
}
|
}
|
||||||
if (obj->is_inverted()) {
|
if (obj->is_inverted()) {
|
||||||
ESP_LOGCONFIG(tag, "%s Inverted: YES", prefix);
|
ESP_LOGCONFIG(tag, "%s Inverted: YES", prefix);
|
||||||
}
|
}
|
||||||
if (!obj->get_device_class_ref().empty()) {
|
LOG_ENTITY_DEVICE_CLASS(tag, prefix, *obj);
|
||||||
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ void TemplateAlarmControlPanel::setup() {
|
|||||||
this->current_state_ = ACP_STATE_DISARMED;
|
this->current_state_ = ACP_STATE_DISARMED;
|
||||||
if (this->restore_mode_ == ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED) {
|
if (this->restore_mode_ == ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED) {
|
||||||
uint8_t value;
|
uint8_t value;
|
||||||
this->pref_ = global_preferences->make_preference<uint8_t>(this->get_preference_hash());
|
this->pref_ = this->make_entity_preference<uint8_t>();
|
||||||
if (this->pref_.load(&value)) {
|
if (this->pref_.load(&value)) {
|
||||||
this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value);
|
this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ void TemplateDate::setup() {
|
|||||||
state = this->initial_value_;
|
state = this->initial_value_;
|
||||||
} else {
|
} else {
|
||||||
datetime::DateEntityRestoreState temp;
|
datetime::DateEntityRestoreState temp;
|
||||||
this->pref_ =
|
this->pref_ = this->make_entity_preference<datetime::DateEntityRestoreState>(194434030U);
|
||||||
global_preferences->make_preference<datetime::DateEntityRestoreState>(194434030U ^ this->get_preference_hash());
|
|
||||||
if (this->pref_.load(&temp)) {
|
if (this->pref_.load(&temp)) {
|
||||||
temp.apply(this);
|
temp.apply(this);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user