mirror of
https://github.com/esphome/esphome.git
synced 2025-07-31 15:37:49 +00:00
Merge branch 'dev' into revert-9439-lib_compat_mode_fix
This commit is contained in:
commit
01ac30f210
@ -1 +1 @@
|
|||||||
90bb12a42dfe2a13378fb292fd67a5fd503b689bcec2be034be366758a5f69c6
|
4df2fc55e977ba821978fac5f1e721ce2338e23647050b7005b4c801b1770739
|
||||||
|
826
.github/workflows/auto-label-pr.yml
vendored
826
.github/workflows/auto-label-pr.yml
vendored
@ -11,51 +11,10 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TARGET_PLATFORMS: |
|
|
||||||
esp32
|
|
||||||
esp8266
|
|
||||||
rp2040
|
|
||||||
libretiny
|
|
||||||
bk72xx
|
|
||||||
rtl87xx
|
|
||||||
ln882x
|
|
||||||
nrf52
|
|
||||||
host
|
|
||||||
PLATFORM_COMPONENTS: |
|
|
||||||
alarm_control_panel
|
|
||||||
audio_adc
|
|
||||||
audio_dac
|
|
||||||
binary_sensor
|
|
||||||
button
|
|
||||||
canbus
|
|
||||||
climate
|
|
||||||
cover
|
|
||||||
datetime
|
|
||||||
display
|
|
||||||
event
|
|
||||||
fan
|
|
||||||
light
|
|
||||||
lock
|
|
||||||
media_player
|
|
||||||
microphone
|
|
||||||
number
|
|
||||||
one_wire
|
|
||||||
ota
|
|
||||||
output
|
|
||||||
packet_transport
|
|
||||||
select
|
|
||||||
sensor
|
|
||||||
speaker
|
|
||||||
stepper
|
|
||||||
switch
|
|
||||||
text
|
|
||||||
text_sensor
|
|
||||||
time
|
|
||||||
touchscreen
|
|
||||||
update
|
|
||||||
valve
|
|
||||||
SMALL_PR_THRESHOLD: 30
|
SMALL_PR_THRESHOLD: 30
|
||||||
MAX_LABELS: 15
|
MAX_LABELS: 15
|
||||||
|
TOO_BIG_THRESHOLD: 1000
|
||||||
|
COMPONENT_LABEL_THRESHOLD: 10
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
label:
|
||||||
@ -65,24 +24,6 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Get changes
|
|
||||||
id: changes
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
# Get PR number
|
|
||||||
pr_number="${{ github.event.pull_request.number }}"
|
|
||||||
|
|
||||||
# Get list of changed files using gh CLI
|
|
||||||
files=$(gh pr diff $pr_number --name-only)
|
|
||||||
echo "files<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "$files" >> $GITHUB_OUTPUT
|
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Get file stats (additions + deletions) using gh CLI
|
|
||||||
stats=$(gh pr view $pr_number --json files --jq '.files | map(.additions + .deletions) | add')
|
|
||||||
echo "total_changes=${stats:-0}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@v2
|
uses: actions/create-github-app-token@v2
|
||||||
@ -97,73 +38,466 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
'core',
|
||||||
|
'small-pr',
|
||||||
|
'dashboard',
|
||||||
|
'github-actions',
|
||||||
|
'by-code-owner',
|
||||||
|
'has-tests',
|
||||||
|
'needs-tests',
|
||||||
|
'needs-docs',
|
||||||
|
'needs-codeowners',
|
||||||
|
'too-big',
|
||||||
|
'labeller-recheck'
|
||||||
|
];
|
||||||
|
|
||||||
|
const DOCS_PR_PATTERNS = [
|
||||||
|
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
||||||
|
/esphome\/esphome-docs#\d+/
|
||||||
|
];
|
||||||
|
|
||||||
|
// Global state
|
||||||
const { owner, repo } = context.repo;
|
const { owner, repo } = context.repo;
|
||||||
const pr_number = context.issue.number;
|
const pr_number = context.issue.number;
|
||||||
|
|
||||||
// Get current labels
|
// Get current labels and PR data
|
||||||
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
const { data: currentLabelsData } = await github.rest.issues.listLabelsOnIssue({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issue_number: pr_number
|
issue_number: pr_number
|
||||||
});
|
});
|
||||||
const currentLabels = currentLabelsData.map(label => label.name);
|
const currentLabels = currentLabelsData.map(label => label.name);
|
||||||
|
|
||||||
// Define managed labels that this workflow controls
|
|
||||||
const managedLabels = currentLabels.filter(label =>
|
const managedLabels = currentLabels.filter(label =>
|
||||||
label.startsWith('component: ') ||
|
label.startsWith('component: ') || MANAGED_LABELS.includes(label)
|
||||||
[
|
|
||||||
'new-component',
|
|
||||||
'new-platform',
|
|
||||||
'new-target-platform',
|
|
||||||
'merging-to-release',
|
|
||||||
'merging-to-beta',
|
|
||||||
'core',
|
|
||||||
'small-pr',
|
|
||||||
'dashboard',
|
|
||||||
'github-actions',
|
|
||||||
'by-code-owner',
|
|
||||||
'has-tests',
|
|
||||||
'needs-tests',
|
|
||||||
'needs-docs',
|
|
||||||
'too-big',
|
|
||||||
'labeller-recheck'
|
|
||||||
].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 totalChanges = prFiles.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
|
|
||||||
console.log('Current labels:', currentLabels.join(', '));
|
console.log('Current labels:', currentLabels.join(', '));
|
||||||
console.log('Managed labels:', managedLabels.join(', '));
|
|
||||||
|
|
||||||
// Get changed files
|
|
||||||
const changedFiles = `${{ steps.changes.outputs.files }}`.split('\n').filter(f => f.length > 0);
|
|
||||||
const totalChanges = parseInt('${{ steps.changes.outputs.total_changes }}') || 0;
|
|
||||||
|
|
||||||
console.log('Changed files:', changedFiles.length);
|
console.log('Changed files:', changedFiles.length);
|
||||||
console.log('Total changes:', totalChanges);
|
console.log('Total changes:', totalChanges);
|
||||||
|
if (isMegaPR) {
|
||||||
|
console.log('Mega-PR detected - applying limited labeling logic');
|
||||||
|
}
|
||||||
|
|
||||||
const labels = new Set();
|
// 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: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get environment variables
|
// Strategy: Merge branch detection
|
||||||
const targetPlatforms = `${{ env.TARGET_PLATFORMS }}`.split('\n').filter(p => p.trim().length > 0).map(p => p.trim());
|
async function detectMergeBranch() {
|
||||||
const platformComponents = `${{ env.PLATFORM_COMPONENTS }}`.split('\n').filter(p => p.trim().length > 0).map(p => p.trim());
|
const labels = new Set();
|
||||||
const smallPrThreshold = parseInt('${{ env.SMALL_PR_THRESHOLD }}');
|
const baseRef = context.payload.pull_request.base.ref;
|
||||||
const maxLabels = parseInt('${{ env.MAX_LABELS }}');
|
|
||||||
|
|
||||||
// Strategy: Merge to release or beta branch
|
|
||||||
const baseRef = context.payload.pull_request.base.ref;
|
|
||||||
if (baseRef !== 'dev') {
|
|
||||||
if (baseRef === 'release') {
|
if (baseRef === 'release') {
|
||||||
labels.add('merging-to-release');
|
labels.add('merging-to-release');
|
||||||
} else if (baseRef === 'beta') {
|
} else if (baseRef === 'beta') {
|
||||||
labels.add('merging-to-beta');
|
labels.add('merging-to-beta');
|
||||||
}
|
}
|
||||||
|
|
||||||
// When targeting non-dev branches, only use merge warning labels
|
return labels;
|
||||||
const finalLabels = Array.from(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();
|
||||||
|
const testChanges = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
|
|
||||||
|
const nonTestChanges = totalChanges - testChanges;
|
||||||
|
|
||||||
|
if (totalChanges <= SMALL_PR_THRESHOLD) {
|
||||||
|
labels.add('small-pr');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: 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('has-tests')) {
|
||||||
|
labels.add('needs-tests');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for missing docs
|
||||||
|
if (allLabels.has('new-component') || allLabels.has('new-platform')) {
|
||||||
|
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) {
|
||||||
|
const messages = [];
|
||||||
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
|
||||||
|
// Too big message
|
||||||
|
if (finalLabels.includes('too-big')) {
|
||||||
|
const testChanges = prFiles
|
||||||
|
.filter(file => file.filename.startsWith('tests/'))
|
||||||
|
.reduce((sum, file) => sum + (file.additions || 0) + (file.deletions || 0), 0);
|
||||||
|
const nonTestChanges = totalChanges - testChanges;
|
||||||
|
|
||||||
|
const tooManyLabels = finalLabels.length > 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 ${finalLabels.length} different components/areas.`;
|
||||||
|
} else if (tooManyLabels) {
|
||||||
|
message += `This PR affects ${finalLabels.length} 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) {
|
||||||
|
const reviewMessages = generateReviewMessages(finalLabels);
|
||||||
|
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 non-dev branches
|
||||||
|
if (baseRef !== 'dev') {
|
||||||
|
const branchLabels = await detectMergeBranch();
|
||||||
|
const finalLabels = Array.from(branchLabels);
|
||||||
|
|
||||||
console.log('Computed labels (merge branch only):', finalLabels.join(', '));
|
console.log('Computed labels (merge branch only):', finalLabels.join(', '));
|
||||||
|
|
||||||
// Add new labels
|
// Apply labels
|
||||||
if (finalLabels.length > 0) {
|
if (finalLabels.length > 0) {
|
||||||
console.log(`Adding labels: ${finalLabels.join(', ')}`);
|
|
||||||
await github.rest.issues.addLabels({
|
await github.rest.issues.addLabels({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
@ -172,13 +506,9 @@ jobs:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old managed labels that are no longer needed
|
// Remove old managed labels
|
||||||
const labelsToRemove = managedLabels.filter(label =>
|
const labelsToRemove = managedLabels.filter(label => !finalLabels.includes(label));
|
||||||
!finalLabels.includes(label)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const label of labelsToRemove) {
|
for (const label of labelsToRemove) {
|
||||||
console.log(`Removing label: ${label}`);
|
|
||||||
try {
|
try {
|
||||||
await github.rest.issues.removeLabel({
|
await github.rest.issues.removeLabel({
|
||||||
owner,
|
owner,
|
||||||
@ -191,235 +521,78 @@ jobs:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return; // Exit early, don't process other strategies
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: Component and Platform labeling
|
// Run all strategies
|
||||||
const componentRegex = /^esphome\/components\/([^\/]+)\//;
|
const [
|
||||||
const targetPlatformRegex = new RegExp(`^esphome\/components\/(${targetPlatforms.join('|')})/`);
|
branchLabels,
|
||||||
|
componentLabels,
|
||||||
|
newComponentLabels,
|
||||||
|
newPlatformLabels,
|
||||||
|
coreLabels,
|
||||||
|
sizeLabels,
|
||||||
|
dashboardLabels,
|
||||||
|
actionsLabels,
|
||||||
|
codeOwnerLabels,
|
||||||
|
testLabels
|
||||||
|
] = await Promise.all([
|
||||||
|
detectMergeBranch(),
|
||||||
|
detectComponentPlatforms(apiData),
|
||||||
|
detectNewComponents(),
|
||||||
|
detectNewPlatforms(apiData),
|
||||||
|
detectCoreChanges(),
|
||||||
|
detectPRSize(),
|
||||||
|
detectDashboardChanges(),
|
||||||
|
detectGitHubActionsChanges(),
|
||||||
|
detectCodeOwner(),
|
||||||
|
detectTests()
|
||||||
|
]);
|
||||||
|
|
||||||
for (const file of changedFiles) {
|
// Combine all labels
|
||||||
// Check for component changes
|
const allLabels = new Set([
|
||||||
const componentMatch = file.match(componentRegex);
|
...branchLabels,
|
||||||
if (componentMatch) {
|
...componentLabels,
|
||||||
const component = componentMatch[1];
|
...newComponentLabels,
|
||||||
labels.add(`component: ${component}`);
|
...newPlatformLabels,
|
||||||
}
|
...coreLabels,
|
||||||
|
...sizeLabels,
|
||||||
|
...dashboardLabels,
|
||||||
|
...actionsLabels,
|
||||||
|
...codeOwnerLabels,
|
||||||
|
...testLabels
|
||||||
|
]);
|
||||||
|
|
||||||
// Check for target platform changes
|
// Detect requirements based on all other labels
|
||||||
const platformMatch = file.match(targetPlatformRegex);
|
const requirementLabels = await detectRequirements(allLabels);
|
||||||
if (platformMatch) {
|
for (const label of requirementLabels) {
|
||||||
const targetPlatform = platformMatch[1];
|
allLabels.add(label);
|
||||||
labels.add(`platform: ${targetPlatform}`);
|
}
|
||||||
|
|
||||||
|
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})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get PR files for new component/platform detection
|
// Handle too many labels (only for non-mega PRs)
|
||||||
const { data: prFiles } = await github.rest.pulls.listFiles({
|
const tooManyLabels = finalLabels.length > MAX_LABELS;
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number
|
|
||||||
});
|
|
||||||
|
|
||||||
const addedFiles = prFiles.filter(file => file.status === 'added').map(file => file.filename);
|
if (tooManyLabels && !isMegaPR && !finalLabels.includes('too-big')) {
|
||||||
|
finalLabels = ['too-big'];
|
||||||
// Strategy: New Component detection
|
|
||||||
for (const file of addedFiles) {
|
|
||||||
// Check for new component files: esphome/components/{component}/__init__.py
|
|
||||||
const componentMatch = file.match(/^esphome\/components\/([^\/]+)\/__init__\.py$/);
|
|
||||||
if (componentMatch) {
|
|
||||||
try {
|
|
||||||
// Read the content directly from the filesystem since we have it checked out
|
|
||||||
const content = fs.readFileSync(file, 'utf8');
|
|
||||||
|
|
||||||
// Strategy: New Target Platform detection
|
|
||||||
if (content.includes('IS_TARGET_PLATFORM = True')) {
|
|
||||||
labels.add('new-target-platform');
|
|
||||||
}
|
|
||||||
labels.add('new-component');
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`Failed to read content of ${file}:`, error.message);
|
|
||||||
// Fallback: assume it's a new component if we can't read the content
|
|
||||||
labels.add('new-component');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy: New Platform detection
|
|
||||||
for (const file of addedFiles) {
|
|
||||||
// Check for new platform files: esphome/components/{component}/{platform}.py
|
|
||||||
const platformFileMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\.py$/);
|
|
||||||
if (platformFileMatch) {
|
|
||||||
const [, component, platform] = platformFileMatch;
|
|
||||||
if (platformComponents.includes(platform)) {
|
|
||||||
labels.add('new-platform');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for new platform files: esphome/components/{component}/{platform}/__init__.py
|
|
||||||
const platformDirMatch = file.match(/^esphome\/components\/([^\/]+)\/([^\/]+)\/__init__\.py$/);
|
|
||||||
if (platformDirMatch) {
|
|
||||||
const [, component, platform] = platformDirMatch;
|
|
||||||
if (platformComponents.includes(platform)) {
|
|
||||||
labels.add('new-platform');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const coreFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('esphome/core/') ||
|
|
||||||
(file.startsWith('esphome/') && file.split('/').length === 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (coreFiles.length > 0) {
|
|
||||||
labels.add('core');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Small PR detection
|
|
||||||
if (totalChanges <= smallPrThreshold) {
|
|
||||||
labels.add('small-pr');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Dashboard changes
|
|
||||||
const dashboardFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('esphome/dashboard/') ||
|
|
||||||
file.startsWith('esphome/components/dashboard_import/')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dashboardFiles.length > 0) {
|
|
||||||
labels.add('dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: GitHub Actions changes
|
|
||||||
const githubActionsFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('.github/workflows/')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (githubActionsFiles.length > 0) {
|
|
||||||
labels.add('github-actions');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Code Owner detection
|
|
||||||
try {
|
|
||||||
// Fetch CODEOWNERS file from the repository (in case it was changed in this PR)
|
|
||||||
const { data: codeownersFile } = await github.rest.repos.getContent({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
path: '.github/CODEOWNERS',
|
|
||||||
ref: context.payload.pull_request.head.sha
|
|
||||||
});
|
|
||||||
|
|
||||||
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
|
||||||
const prAuthor = context.payload.pull_request.user.login;
|
|
||||||
|
|
||||||
// Parse CODEOWNERS file
|
|
||||||
const codeownersLines = codeownersContent.split('\n')
|
|
||||||
.map(line => line.trim())
|
|
||||||
.filter(line => line && !line.startsWith('#'));
|
|
||||||
|
|
||||||
let isCodeOwner = false;
|
|
||||||
|
|
||||||
// Precompile CODEOWNERS patterns into regex objects
|
|
||||||
const codeownersRegexes = codeownersLines.map(line => {
|
|
||||||
const parts = line.split(/\s+/);
|
|
||||||
const pattern = parts[0];
|
|
||||||
const owners = parts.slice(1);
|
|
||||||
|
|
||||||
let regex;
|
|
||||||
if (pattern.endsWith('*')) {
|
|
||||||
// Directory pattern like "esphome/components/api/*"
|
|
||||||
const dir = pattern.slice(0, -1);
|
|
||||||
regex = new RegExp(`^${dir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
|
|
||||||
} else if (pattern.includes('*')) {
|
|
||||||
// Glob pattern
|
|
||||||
const regexPattern = pattern
|
|
||||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
||||||
.replace(/\\*/g, '.*');
|
|
||||||
regex = new RegExp(`^${regexPattern}$`);
|
|
||||||
} else {
|
|
||||||
// Exact match
|
|
||||||
regex = new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { regex, owners };
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const file of changedFiles) {
|
|
||||||
for (const { regex, owners } of codeownersRegexes) {
|
|
||||||
if (regex.test(file)) {
|
|
||||||
// Check if PR author is in the owners list
|
|
||||||
if (owners.some(owner => owner === `@${prAuthor}`)) {
|
|
||||||
isCodeOwner = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isCodeOwner) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCodeOwner) {
|
|
||||||
labels.add('by-code-owner');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Failed to read or parse CODEOWNERS file:', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Test detection
|
|
||||||
const testFiles = changedFiles.filter(file =>
|
|
||||||
file.startsWith('tests/')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (testFiles.length > 0) {
|
|
||||||
labels.add('has-tests');
|
|
||||||
} else {
|
|
||||||
// Only check for needs-tests if this is a new component or new platform
|
|
||||||
if (labels.has('new-component') || labels.has('new-platform')) {
|
|
||||||
labels.add('needs-tests');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy: Documentation check for new components/platforms
|
|
||||||
if (labels.has('new-component') || labels.has('new-platform')) {
|
|
||||||
const prBody = context.payload.pull_request.body || '';
|
|
||||||
|
|
||||||
// Look for documentation PR links
|
|
||||||
// Patterns to match:
|
|
||||||
// - https://github.com/esphome/esphome-docs/pull/1234
|
|
||||||
// - esphome/esphome-docs#1234
|
|
||||||
const docsPrPatterns = [
|
|
||||||
/https:\/\/github\.com\/esphome\/esphome-docs\/pull\/\d+/,
|
|
||||||
/esphome\/esphome-docs#\d+/
|
|
||||||
];
|
|
||||||
|
|
||||||
const hasDocsLink = docsPrPatterns.some(pattern => pattern.test(prBody));
|
|
||||||
|
|
||||||
if (!hasDocsLink) {
|
|
||||||
labels.add('needs-docs');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Set to Array
|
|
||||||
let finalLabels = Array.from(labels);
|
|
||||||
|
|
||||||
console.log('Computed labels:', finalLabels.join(', '));
|
console.log('Computed labels:', finalLabels.join(', '));
|
||||||
|
|
||||||
// Don't set more than max labels
|
// Handle reviews
|
||||||
if (finalLabels.length > maxLabels) {
|
await handleReviews(finalLabels);
|
||||||
const originalLength = finalLabels.length;
|
|
||||||
console.log(`Not setting ${originalLength} labels because out of range`);
|
|
||||||
finalLabels = ['too-big'];
|
|
||||||
|
|
||||||
// Request changes on the PR
|
// Apply labels
|
||||||
await github.rest.pulls.createReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: pr_number,
|
|
||||||
body: `This PR is too large and affects ${originalLength} different components/areas. Please consider breaking it down into smaller, focused PRs to make review easier and reduce the risk of conflicts.`,
|
|
||||||
event: 'REQUEST_CHANGES'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new labels
|
|
||||||
if (finalLabels.length > 0) {
|
if (finalLabels.length > 0) {
|
||||||
console.log(`Adding labels: ${finalLabels.join(', ')}`);
|
console.log(`Adding labels: ${finalLabels.join(', ')}`);
|
||||||
await github.rest.issues.addLabels({
|
await github.rest.issues.addLabels({
|
||||||
@ -430,11 +603,8 @@ jobs:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old managed labels that are no longer needed
|
// Remove old managed labels
|
||||||
const labelsToRemove = managedLabels.filter(label =>
|
const labelsToRemove = managedLabels.filter(label => !finalLabels.includes(label));
|
||||||
!finalLabels.includes(label)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const label of labelsToRemove) {
|
for (const label of labelsToRemove) {
|
||||||
console.log(`Removing label: ${label}`);
|
console.log(`Removing label: ${label}`);
|
||||||
try {
|
try {
|
||||||
|
324
.github/workflows/codeowner-review-request.yml
vendored
Normal file
324
.github/workflows/codeowner-review-request.yml
vendored
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
# This workflow automatically requests reviews from codeowners when:
|
||||||
|
# 1. A PR is opened, reopened, or synchronized (updated)
|
||||||
|
# 2. A PR is marked as ready for review
|
||||||
|
#
|
||||||
|
# It reads the CODEOWNERS file and matches all changed files in the PR against
|
||||||
|
# the codeowner patterns, then requests reviews from the appropriate owners
|
||||||
|
# while avoiding duplicate requests for users who have already been requested
|
||||||
|
# or have already reviewed the PR.
|
||||||
|
|
||||||
|
name: Request Codeowner Reviews
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Needs to be pull_request_target to get write permissions
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, reopened, synchronize, ready_for_review]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
request-codeowner-reviews:
|
||||||
|
name: Run
|
||||||
|
if: ${{ !github.event.pull_request.draft }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Request reviews from component codeowners
|
||||||
|
uses: actions/github-script@v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const pr_number = context.payload.pull_request.number;
|
||||||
|
|
||||||
|
console.log(`Processing PR #${pr_number} for codeowner review requests`);
|
||||||
|
|
||||||
|
// Hidden marker to identify bot comments from this workflow
|
||||||
|
const BOT_COMMENT_MARKER = '<!-- codeowner-review-request-bot -->';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the list of changed files in this PR
|
||||||
|
const { data: files } = await github.rest.pulls.listFiles({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
const changedFiles = files.map(file => file.filename);
|
||||||
|
console.log(`Found ${changedFiles.length} changed files`);
|
||||||
|
|
||||||
|
if (changedFiles.length === 0) {
|
||||||
|
console.log('No changed files found, skipping codeowner review requests');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch CODEOWNERS file from root
|
||||||
|
const { data: codeownersFile } = await github.rest.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: 'CODEOWNERS',
|
||||||
|
ref: context.payload.pull_request.base.sha
|
||||||
|
});
|
||||||
|
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
// Parse CODEOWNERS file to extract all patterns and their owners
|
||||||
|
const codeownersLines = codeownersContent.split('\n')
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(line => line && !line.startsWith('#'));
|
||||||
|
|
||||||
|
const codeownersPatterns = [];
|
||||||
|
|
||||||
|
// Convert CODEOWNERS pattern to regex (robust glob handling)
|
||||||
|
function globToRegex(pattern) {
|
||||||
|
// Escape regex special characters except for glob wildcards
|
||||||
|
let regexStr = pattern
|
||||||
|
.replace(/([.+^=!:${}()|[\]\\])/g, '\\$1') // escape regex chars
|
||||||
|
.replace(/\*\*/g, '.*') // globstar
|
||||||
|
.replace(/\*/g, '[^/]*') // single star
|
||||||
|
.replace(/\?/g, '.'); // question mark
|
||||||
|
return new RegExp('^' + regexStr + '$');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to create comment body
|
||||||
|
function createCommentBody(reviewersList, teamsList, matchedFileCount, isSuccessful = true) {
|
||||||
|
const reviewerMentions = reviewersList.map(r => `@${r}`);
|
||||||
|
const teamMentions = teamsList.map(t => `@${owner}/${t}`);
|
||||||
|
const allMentions = [...reviewerMentions, ...teamMentions].join(', ');
|
||||||
|
|
||||||
|
if (isSuccessful) {
|
||||||
|
return `${BOT_COMMENT_MARKER}\n👋 Hi there! I've automatically requested reviews from codeowners based on the files changed in this PR.\n\n${allMentions} - You've been requested to review this PR as codeowner(s) of ${matchedFileCount} file(s) that were modified. Thanks for your time! 🙏`;
|
||||||
|
} else {
|
||||||
|
return `${BOT_COMMENT_MARKER}\n👋 Hi there! This PR modifies ${matchedFileCount} file(s) with codeowners.\n\n${allMentions} - As codeowner(s) of the affected files, your review would be appreciated! 🙏\n\n_Note: Automatic review request may have failed, but you're still welcome to review._`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const line of codeownersLines) {
|
||||||
|
const parts = line.split(/\s+/);
|
||||||
|
if (parts.length < 2) continue;
|
||||||
|
|
||||||
|
const pattern = parts[0];
|
||||||
|
const owners = parts.slice(1);
|
||||||
|
|
||||||
|
// Use robust glob-to-regex conversion
|
||||||
|
const regex = globToRegex(pattern);
|
||||||
|
codeownersPatterns.push({ pattern, regex, owners });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Parsed ${codeownersPatterns.length} codeowner patterns`);
|
||||||
|
|
||||||
|
// Match changed files against CODEOWNERS patterns
|
||||||
|
const matchedOwners = new Set();
|
||||||
|
const matchedTeams = new Set();
|
||||||
|
const fileMatches = new Map(); // Track which files matched which patterns
|
||||||
|
|
||||||
|
for (const file of changedFiles) {
|
||||||
|
for (const { pattern, regex, owners } of codeownersPatterns) {
|
||||||
|
if (regex.test(file)) {
|
||||||
|
console.log(`File '${file}' matches pattern '${pattern}' with owners: ${owners.join(', ')}`);
|
||||||
|
|
||||||
|
if (!fileMatches.has(file)) {
|
||||||
|
fileMatches.set(file, []);
|
||||||
|
}
|
||||||
|
fileMatches.get(file).push({ pattern, owners });
|
||||||
|
|
||||||
|
// Add owners to the appropriate set (remove @ prefix)
|
||||||
|
for (const owner of owners) {
|
||||||
|
const cleanOwner = owner.startsWith('@') ? owner.slice(1) : owner;
|
||||||
|
if (cleanOwner.includes('/')) {
|
||||||
|
// Team mention (org/team-name)
|
||||||
|
const teamName = cleanOwner.split('/')[1];
|
||||||
|
matchedTeams.add(teamName);
|
||||||
|
} else {
|
||||||
|
// Individual user
|
||||||
|
matchedOwners.add(cleanOwner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedOwners.size === 0 && matchedTeams.size === 0) {
|
||||||
|
console.log('No codeowners found for any changed files');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the PR author from reviewers
|
||||||
|
const prAuthor = context.payload.pull_request.user.login;
|
||||||
|
matchedOwners.delete(prAuthor);
|
||||||
|
|
||||||
|
// Get current reviewers to avoid duplicate requests (but still mention them)
|
||||||
|
const { data: prData } = await github.rest.pulls.get({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentReviewers = new Set();
|
||||||
|
const currentTeams = new Set();
|
||||||
|
|
||||||
|
if (prData.requested_reviewers) {
|
||||||
|
prData.requested_reviewers.forEach(reviewer => {
|
||||||
|
currentReviewers.add(reviewer.login);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prData.requested_teams) {
|
||||||
|
prData.requested_teams.forEach(team => {
|
||||||
|
currentTeams.add(team.slug);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for completed reviews to avoid re-requesting users who have already reviewed
|
||||||
|
const { data: reviews } = await github.rest.pulls.listReviews({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviewedUsers = new Set();
|
||||||
|
reviews.forEach(review => {
|
||||||
|
reviewedUsers.add(review.user.login);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for previous comments from this workflow to avoid duplicate pings
|
||||||
|
const comments = await github.paginate(
|
||||||
|
github.rest.issues.listComments,
|
||||||
|
{
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const previouslyPingedUsers = new Set();
|
||||||
|
const previouslyPingedTeams = new Set();
|
||||||
|
|
||||||
|
// Look for comments from github-actions bot that contain our bot marker
|
||||||
|
const workflowComments = comments.filter(comment =>
|
||||||
|
comment.user.type === 'Bot' &&
|
||||||
|
comment.body.includes(BOT_COMMENT_MARKER)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract previously mentioned users and teams from workflow comments
|
||||||
|
for (const comment of workflowComments) {
|
||||||
|
// Match @username patterns (not team mentions)
|
||||||
|
const userMentions = comment.body.match(/@([a-zA-Z0-9_.-]+)(?![/])/g) || [];
|
||||||
|
userMentions.forEach(mention => {
|
||||||
|
const username = mention.slice(1); // remove @
|
||||||
|
previouslyPingedUsers.add(username);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Match @org/team patterns
|
||||||
|
const teamMentions = comment.body.match(/@[a-zA-Z0-9_.-]+\/([a-zA-Z0-9_.-]+)/g) || [];
|
||||||
|
teamMentions.forEach(mention => {
|
||||||
|
const teamName = mention.split('/')[1];
|
||||||
|
previouslyPingedTeams.add(teamName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${previouslyPingedUsers.size} previously pinged users and ${previouslyPingedTeams.size} previously pinged teams`);
|
||||||
|
|
||||||
|
// Remove users who have already been pinged in previous workflow comments
|
||||||
|
previouslyPingedUsers.forEach(user => {
|
||||||
|
matchedOwners.delete(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
previouslyPingedTeams.forEach(team => {
|
||||||
|
matchedTeams.delete(team);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove only users who have already submitted reviews (not just requested reviewers)
|
||||||
|
reviewedUsers.forEach(reviewer => {
|
||||||
|
matchedOwners.delete(reviewer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// For teams, we'll still remove already requested teams to avoid API errors
|
||||||
|
currentTeams.forEach(team => {
|
||||||
|
matchedTeams.delete(team);
|
||||||
|
});
|
||||||
|
|
||||||
|
const reviewersList = Array.from(matchedOwners);
|
||||||
|
const teamsList = Array.from(matchedTeams);
|
||||||
|
|
||||||
|
if (reviewersList.length === 0 && teamsList.length === 0) {
|
||||||
|
console.log('No eligible reviewers found (all may already be requested, reviewed, or previously pinged)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalReviewers = reviewersList.length + teamsList.length;
|
||||||
|
console.log(`Requesting reviews from ${reviewersList.length} users and ${teamsList.length} teams for ${fileMatches.size} matched files`);
|
||||||
|
|
||||||
|
// Request reviews
|
||||||
|
try {
|
||||||
|
const requestParams = {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: pr_number
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter out users who are already requested reviewers for the API call
|
||||||
|
const newReviewers = reviewersList.filter(reviewer => !currentReviewers.has(reviewer));
|
||||||
|
const newTeams = teamsList.filter(team => !currentTeams.has(team));
|
||||||
|
|
||||||
|
if (newReviewers.length > 0) {
|
||||||
|
requestParams.reviewers = newReviewers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newTeams.length > 0) {
|
||||||
|
requestParams.team_reviewers = newTeams;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only make the API call if there are new reviewers to request
|
||||||
|
if (newReviewers.length > 0 || newTeams.length > 0) {
|
||||||
|
await github.rest.pulls.requestReviewers(requestParams);
|
||||||
|
console.log(`Successfully requested reviews from ${newReviewers.length} new users and ${newTeams.length} new teams`);
|
||||||
|
} else {
|
||||||
|
console.log('All codeowners are already requested reviewers or have reviewed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add a comment if there are new codeowners to mention (not previously pinged)
|
||||||
|
if (reviewersList.length > 0 || teamsList.length > 0) {
|
||||||
|
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, true);
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
console.log(`Added comment mentioning ${reviewersList.length} users and ${teamsList.length} teams`);
|
||||||
|
} else {
|
||||||
|
console.log('No new codeowners to mention in comment (all previously pinged)');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.status === 422) {
|
||||||
|
console.log('Some reviewers may already be requested or unavailable:', error.message);
|
||||||
|
|
||||||
|
// Only try to add a comment if there are new codeowners to mention
|
||||||
|
if (reviewersList.length > 0 || teamsList.length > 0) {
|
||||||
|
const commentBody = createCommentBody(reviewersList, teamsList, fileMatches.size, false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: pr_number,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
console.log(`Added fallback comment mentioning ${reviewersList.length} users and ${teamsList.length} teams`);
|
||||||
|
} catch (commentError) {
|
||||||
|
console.log('Failed to add comment:', commentError.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No new codeowners to mention in fallback comment');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to process codeowner review requests:', error.message);
|
||||||
|
console.error(error);
|
||||||
|
}
|
26
.github/workflows/external-component-bot.yml
vendored
26
.github/workflows/external-component-bot.yml
vendored
@ -61,7 +61,8 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createComment(octokit, owner, repo, prNumber, esphomeChanges, componentChanges) {
|
async function createComment(octokit, owner, repo, prNumber, esphomeChanges, componentChanges) {
|
||||||
const commentMarker = "<!-- This comment was generated automatically by a GitHub workflow. -->";
|
const commentMarker = "<!-- This comment was generated automatically by the external-component-bot workflow. -->";
|
||||||
|
const legacyCommentMarker = "<!-- This comment was generated automatically by a GitHub workflow. -->";
|
||||||
let commentBody;
|
let commentBody;
|
||||||
if (esphomeChanges.length === 1) {
|
if (esphomeChanges.length === 1) {
|
||||||
commentBody = generateExternalComponentInstructions(prNumber, componentChanges, owner, repo);
|
commentBody = generateExternalComponentInstructions(prNumber, componentChanges, owner, repo);
|
||||||
@ -71,14 +72,23 @@ jobs:
|
|||||||
commentBody += `\n\n---\n(Added by the PR bot)\n\n${commentMarker}`;
|
commentBody += `\n\n---\n(Added by the PR bot)\n\n${commentMarker}`;
|
||||||
|
|
||||||
// Check for existing bot comment
|
// Check for existing bot comment
|
||||||
const comments = await github.rest.issues.listComments({
|
const comments = await github.paginate(
|
||||||
owner: owner,
|
github.rest.issues.listComments,
|
||||||
repo: repo,
|
{
|
||||||
issue_number: prNumber,
|
owner: owner,
|
||||||
});
|
repo: repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
per_page: 100,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const botComment = comments.data.find(comment =>
|
const sorted = comments.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at));
|
||||||
comment.body.includes(commentMarker)
|
|
||||||
|
const botComment = sorted.find(comment =>
|
||||||
|
(
|
||||||
|
comment.body.includes(commentMarker) ||
|
||||||
|
comment.body.includes(legacyCommentMarker)
|
||||||
|
) && comment.user.type === "Bot"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (botComment && botComment.body === commentBody) {
|
if (botComment && botComment.body === commentBody) {
|
||||||
|
163
.github/workflows/issue-codeowner-notify.yml
vendored
Normal file
163
.github/workflows/issue-codeowner-notify.yml
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# This workflow automatically notifies codeowners when an issue is labeled with component labels.
|
||||||
|
# It reads the CODEOWNERS file to find the maintainers for the labeled components
|
||||||
|
# and posts a comment mentioning them to ensure they're aware of the issue.
|
||||||
|
|
||||||
|
name: Notify Issue Codeowners
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
notify-codeowners:
|
||||||
|
name: Run
|
||||||
|
if: ${{ startsWith(github.event.label.name, format('component{0} ', ':')) }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Notify codeowners for component issues
|
||||||
|
uses: actions/github-script@v7.0.1
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
const issue_number = context.payload.issue.number;
|
||||||
|
const labelName = context.payload.label.name;
|
||||||
|
|
||||||
|
console.log(`Processing issue #${issue_number} with label: ${labelName}`);
|
||||||
|
|
||||||
|
// Hidden marker to identify bot comments from this workflow
|
||||||
|
const BOT_COMMENT_MARKER = '<!-- issue-codeowner-notify-bot -->';
|
||||||
|
|
||||||
|
// Extract component name from label
|
||||||
|
const componentName = labelName.replace('component: ', '');
|
||||||
|
console.log(`Component: ${componentName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch CODEOWNERS file from root
|
||||||
|
const { data: codeownersFile } = await github.rest.repos.getContent({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
path: 'CODEOWNERS'
|
||||||
|
});
|
||||||
|
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf8');
|
||||||
|
|
||||||
|
// Parse CODEOWNERS file to extract component mappings
|
||||||
|
const codeownersLines = codeownersContent.split('\n')
|
||||||
|
.map(line => line.trim())
|
||||||
|
.filter(line => line && !line.startsWith('#'));
|
||||||
|
|
||||||
|
let componentOwners = null;
|
||||||
|
|
||||||
|
for (const line of codeownersLines) {
|
||||||
|
const parts = line.split(/\s+/);
|
||||||
|
if (parts.length < 2) continue;
|
||||||
|
|
||||||
|
const pattern = parts[0];
|
||||||
|
const owners = parts.slice(1);
|
||||||
|
|
||||||
|
// Look for component patterns: esphome/components/{component}/*
|
||||||
|
const componentMatch = pattern.match(/^esphome\/components\/([^\/]+)\/\*$/);
|
||||||
|
if (componentMatch && componentMatch[1] === componentName) {
|
||||||
|
componentOwners = owners;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!componentOwners) {
|
||||||
|
console.log(`No codeowners found for component: ${componentName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found codeowners for '${componentName}': ${componentOwners.join(', ')}`);
|
||||||
|
|
||||||
|
// Separate users and teams
|
||||||
|
const userOwners = [];
|
||||||
|
const teamOwners = [];
|
||||||
|
|
||||||
|
for (const owner of componentOwners) {
|
||||||
|
const cleanOwner = owner.startsWith('@') ? owner.slice(1) : owner;
|
||||||
|
if (cleanOwner.includes('/')) {
|
||||||
|
// Team mention (org/team-name)
|
||||||
|
teamOwners.push(`@${cleanOwner}`);
|
||||||
|
} else {
|
||||||
|
// Individual user
|
||||||
|
userOwners.push(`@${cleanOwner}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove issue author from mentions to avoid self-notification
|
||||||
|
const issueAuthor = context.payload.issue.user.login;
|
||||||
|
const filteredUserOwners = userOwners.filter(mention =>
|
||||||
|
mention !== `@${issueAuthor}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for previous comments from this workflow to avoid duplicate pings
|
||||||
|
const comments = await github.paginate(
|
||||||
|
github.rest.issues.listComments,
|
||||||
|
{
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue_number
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const previouslyPingedUsers = new Set();
|
||||||
|
const previouslyPingedTeams = new Set();
|
||||||
|
|
||||||
|
// Look for comments from github-actions bot that contain codeowner pings for this component
|
||||||
|
const workflowComments = comments.filter(comment =>
|
||||||
|
comment.user.type === 'Bot' &&
|
||||||
|
comment.body.includes(BOT_COMMENT_MARKER) &&
|
||||||
|
comment.body.includes(`component: ${componentName}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract previously mentioned users and teams from workflow comments
|
||||||
|
for (const comment of workflowComments) {
|
||||||
|
// Match @username patterns (not team mentions)
|
||||||
|
const userMentions = comment.body.match(/@([a-zA-Z0-9_.-]+)(?![/])/g) || [];
|
||||||
|
userMentions.forEach(mention => {
|
||||||
|
previouslyPingedUsers.add(mention); // Keep @ prefix for easy comparison
|
||||||
|
});
|
||||||
|
|
||||||
|
// Match @org/team patterns
|
||||||
|
const teamMentions = comment.body.match(/@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+/g) || [];
|
||||||
|
teamMentions.forEach(mention => {
|
||||||
|
previouslyPingedTeams.add(mention);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${previouslyPingedUsers.size} previously pinged users and ${previouslyPingedTeams.size} previously pinged teams for component ${componentName}`);
|
||||||
|
|
||||||
|
// Remove previously pinged users and teams
|
||||||
|
const newUserOwners = filteredUserOwners.filter(mention => !previouslyPingedUsers.has(mention));
|
||||||
|
const newTeamOwners = teamOwners.filter(mention => !previouslyPingedTeams.has(mention));
|
||||||
|
|
||||||
|
const allMentions = [...newUserOwners, ...newTeamOwners];
|
||||||
|
|
||||||
|
if (allMentions.length === 0) {
|
||||||
|
console.log('No new codeowners to notify (all previously pinged or issue author is the only codeowner)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create comment body
|
||||||
|
const mentionString = allMentions.join(', ');
|
||||||
|
const commentBody = `${BOT_COMMENT_MARKER}\n👋 Hey ${mentionString}!\n\nThis issue has been labeled with \`component: ${componentName}\` and you've been identified as a codeowner of this component. Please take a look when you have a chance!\n\nThanks for maintaining this component! 🙏`;
|
||||||
|
|
||||||
|
// Post comment
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: issue_number,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Successfully notified new codeowners: ${mentionString}`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to process codeowner notifications:', error.message);
|
||||||
|
console.error(error);
|
||||||
|
}
|
@ -11,7 +11,7 @@ ci:
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.12.4
|
rev: v0.12.5
|
||||||
hooks:
|
hooks:
|
||||||
# Run the linter.
|
# Run the linter.
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
pyproject.toml @esphome/core
|
pyproject.toml @esphome/core
|
||||||
esphome/*.py @esphome/core
|
esphome/*.py @esphome/core
|
||||||
esphome/core/* @esphome/core
|
esphome/core/* @esphome/core
|
||||||
|
.github/** @esphome/core
|
||||||
|
|
||||||
# Integrations
|
# Integrations
|
||||||
esphome/components/a01nyub/* @MrSuicideParrot
|
esphome/components/a01nyub/* @MrSuicideParrot
|
||||||
@ -245,6 +246,7 @@ esphome/components/lcd_menu/* @numo68
|
|||||||
esphome/components/ld2410/* @regevbr @sebcaps
|
esphome/components/ld2410/* @regevbr @sebcaps
|
||||||
esphome/components/ld2420/* @descipher
|
esphome/components/ld2420/* @descipher
|
||||||
esphome/components/ld2450/* @hareeshmu
|
esphome/components/ld2450/* @hareeshmu
|
||||||
|
esphome/components/ld24xx/* @kbx81
|
||||||
esphome/components/ledc/* @OttoWinter
|
esphome/components/ledc/* @OttoWinter
|
||||||
esphome/components/libretiny/* @kuba2k2
|
esphome/components/libretiny/* @kuba2k2
|
||||||
esphome/components/libretiny_pwm/* @kuba2k2
|
esphome/components/libretiny_pwm/* @kuba2k2
|
||||||
@ -292,6 +294,7 @@ esphome/components/microphone/* @jesserockz @kahrendt
|
|||||||
esphome/components/mics_4514/* @jesserockz
|
esphome/components/mics_4514/* @jesserockz
|
||||||
esphome/components/midea/* @dudanov
|
esphome/components/midea/* @dudanov
|
||||||
esphome/components/midea_ir/* @dudanov
|
esphome/components/midea_ir/* @dudanov
|
||||||
|
esphome/components/mipi_dsi/* @clydebarrow
|
||||||
esphome/components/mipi_spi/* @clydebarrow
|
esphome/components/mipi_spi/* @clydebarrow
|
||||||
esphome/components/mitsubishi/* @RubyBailey
|
esphome/components/mitsubishi/* @RubyBailey
|
||||||
esphome/components/mixer/speaker/* @kahrendt
|
esphome/components/mixer/speaker/* @kahrendt
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import functools
|
import functools
|
||||||
|
import getpass
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -34,6 +35,7 @@ from esphome.const import (
|
|||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_SUBSTITUTIONS,
|
CONF_SUBSTITUTIONS,
|
||||||
CONF_TOPIC,
|
CONF_TOPIC,
|
||||||
|
ENV_NOGITIGNORE,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
@ -88,9 +90,9 @@ def choose_prompt(options, purpose: str = None):
|
|||||||
def choose_upload_log_host(
|
def choose_upload_log_host(
|
||||||
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
default, check_default, show_ota, show_mqtt, show_api, purpose: str = None
|
||||||
):
|
):
|
||||||
options = []
|
options = [
|
||||||
for port in get_serial_ports():
|
(f"{port.path} ({port.description})", port.path) for port in get_serial_ports()
|
||||||
options.append((f"{port.path} ({port.description})", port.path))
|
]
|
||||||
if default == "SERIAL":
|
if default == "SERIAL":
|
||||||
return choose_prompt(options, purpose=purpose)
|
return choose_prompt(options, purpose=purpose)
|
||||||
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
if (show_ota and "ota" in CORE.config) or (show_api and "api" in CORE.config):
|
||||||
@ -118,9 +120,7 @@ def mqtt_logging_enabled(mqtt_config):
|
|||||||
return False
|
return False
|
||||||
if CONF_TOPIC not in log_topic:
|
if CONF_TOPIC not in log_topic:
|
||||||
return False
|
return False
|
||||||
if log_topic.get(CONF_LEVEL, None) == "NONE":
|
return log_topic.get(CONF_LEVEL, None) != "NONE"
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_port_type(port):
|
def get_port_type(port):
|
||||||
@ -209,6 +209,9 @@ def wrap_to_code(name, comp):
|
|||||||
|
|
||||||
|
|
||||||
def write_cpp(config):
|
def write_cpp(config):
|
||||||
|
if not get_bool_env(ENV_NOGITIGNORE):
|
||||||
|
writer.write_gitignore()
|
||||||
|
|
||||||
generate_cpp_contents(config)
|
generate_cpp_contents(config)
|
||||||
return write_cpp_file()
|
return write_cpp_file()
|
||||||
|
|
||||||
@ -225,10 +228,13 @@ def generate_cpp_contents(config):
|
|||||||
|
|
||||||
|
|
||||||
def write_cpp_file():
|
def write_cpp_file():
|
||||||
writer.write_platformio_project()
|
|
||||||
|
|
||||||
code_s = indent(CORE.cpp_main_section)
|
code_s = indent(CORE.cpp_main_section)
|
||||||
writer.write_cpp(code_s)
|
writer.write_cpp(code_s)
|
||||||
|
|
||||||
|
from esphome.build_gen import platformio
|
||||||
|
|
||||||
|
platformio.write_project()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -330,7 +336,7 @@ def check_permissions(port):
|
|||||||
raise EsphomeError(
|
raise EsphomeError(
|
||||||
"You do not have read or write permission on the selected serial port. "
|
"You do not have read or write permission on the selected serial port. "
|
||||||
"To resolve this issue, you can add your user to the dialout group "
|
"To resolve this issue, you can add your user to the dialout group "
|
||||||
f"by running the following command: sudo usermod -a -G dialout {os.getlogin()}. "
|
f"by running the following command: sudo usermod -a -G dialout {getpass.getuser()}. "
|
||||||
"You will need to log out & back in or reboot to activate the new group access."
|
"You will need to log out & back in or reboot to activate the new group access."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
0
esphome/build_gen/__init__.py
Normal file
0
esphome/build_gen/__init__.py
Normal file
102
esphome/build_gen/platformio.py
Normal file
102
esphome/build_gen/platformio.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from esphome.const import __version__
|
||||||
|
from esphome.core import CORE
|
||||||
|
from esphome.helpers import mkdir_p, read_file, write_file_if_changed
|
||||||
|
from esphome.writer import find_begin_end, update_storage_json
|
||||||
|
|
||||||
|
INI_AUTO_GENERATE_BEGIN = "; ========== AUTO GENERATED CODE BEGIN ==========="
|
||||||
|
INI_AUTO_GENERATE_END = "; =========== AUTO GENERATED CODE END ============"
|
||||||
|
|
||||||
|
INI_BASE_FORMAT = (
|
||||||
|
"""; Auto generated code by esphome
|
||||||
|
|
||||||
|
[common]
|
||||||
|
lib_deps =
|
||||||
|
build_flags =
|
||||||
|
upload_flags =
|
||||||
|
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_ini(data: dict[str, str | list[str]]) -> str:
|
||||||
|
content = ""
|
||||||
|
for key, value in sorted(data.items()):
|
||||||
|
if isinstance(value, list):
|
||||||
|
content += f"{key} =\n"
|
||||||
|
for x in value:
|
||||||
|
content += f" {x}\n"
|
||||||
|
else:
|
||||||
|
content += f"{key} = {value}\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def get_ini_content():
|
||||||
|
CORE.add_platformio_option(
|
||||||
|
"lib_deps",
|
||||||
|
[x.as_lib_dep for x in CORE.platformio_libraries.values()]
|
||||||
|
+ ["${common.lib_deps}"],
|
||||||
|
)
|
||||||
|
# Sort to avoid changing build flags order
|
||||||
|
CORE.add_platformio_option("build_flags", sorted(CORE.build_flags))
|
||||||
|
|
||||||
|
# Sort to avoid changing build unflags order
|
||||||
|
CORE.add_platformio_option("build_unflags", sorted(CORE.build_unflags))
|
||||||
|
|
||||||
|
# Add extra script for C++ flags
|
||||||
|
CORE.add_platformio_option("extra_scripts", [f"pre:{CXX_FLAGS_FILE_NAME}"])
|
||||||
|
|
||||||
|
content = "[platformio]\n"
|
||||||
|
content += f"description = ESPHome {__version__}\n"
|
||||||
|
|
||||||
|
content += f"[env:{CORE.name}]\n"
|
||||||
|
content += format_ini(CORE.platformio_options)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def write_ini(content):
|
||||||
|
update_storage_json()
|
||||||
|
path = CORE.relative_build_path("platformio.ini")
|
||||||
|
|
||||||
|
if os.path.isfile(path):
|
||||||
|
text = read_file(path)
|
||||||
|
content_format = find_begin_end(
|
||||||
|
text, INI_AUTO_GENERATE_BEGIN, INI_AUTO_GENERATE_END
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
content_format = INI_BASE_FORMAT
|
||||||
|
full_file = f"{content_format[0] + INI_AUTO_GENERATE_BEGIN}\n{content}"
|
||||||
|
full_file += INI_AUTO_GENERATE_END + content_format[1]
|
||||||
|
write_file_if_changed(path, full_file)
|
||||||
|
|
||||||
|
|
||||||
|
def write_project():
|
||||||
|
mkdir_p(CORE.build_path)
|
||||||
|
|
||||||
|
content = get_ini_content()
|
||||||
|
write_ini(content)
|
||||||
|
|
||||||
|
# Write extra script for C++ specific flags
|
||||||
|
write_cxx_flags_script()
|
||||||
|
|
||||||
|
|
||||||
|
CXX_FLAGS_FILE_NAME = "cxx_flags.py"
|
||||||
|
CXX_FLAGS_FILE_CONTENTS = """# Auto-generated ESPHome script for C++ specific compiler flags
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
# Add C++ specific flags
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def write_cxx_flags_script() -> None:
|
||||||
|
path = CORE.relative_build_path(CXX_FLAGS_FILE_NAME)
|
||||||
|
contents = CXX_FLAGS_FILE_CONTENTS
|
||||||
|
if not CORE.is_host:
|
||||||
|
contents += 'env.Append(CXXFLAGS=["-Wno-volatile"])'
|
||||||
|
contents += "\n"
|
||||||
|
write_file_if_changed(path, contents)
|
@ -7,7 +7,6 @@ namespace a4988 {
|
|||||||
static const char *const TAG = "a4988.stepper";
|
static const char *const TAG = "a4988.stepper";
|
||||||
|
|
||||||
void A4988::setup() {
|
void A4988::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (this->sleep_pin_ != nullptr) {
|
if (this->sleep_pin_ != nullptr) {
|
||||||
this->sleep_pin_->setup();
|
this->sleep_pin_->setup();
|
||||||
this->sleep_pin_->digital_write(false);
|
this->sleep_pin_->digital_write(false);
|
||||||
|
@ -7,8 +7,6 @@ namespace absolute_humidity {
|
|||||||
static const char *const TAG = "absolute_humidity.sensor";
|
static const char *const TAG = "absolute_humidity.sensor";
|
||||||
|
|
||||||
void AbsoluteHumidityComponent::setup() {
|
void AbsoluteHumidityComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
ESP_LOGD(TAG, " Added callback for temperature '%s'", this->temperature_sensor_->get_name().c_str());
|
||||||
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
this->temperature_sensor_->add_on_state_callback([this](float state) { this->temperature_callback_(state); });
|
||||||
if (this->temperature_sensor_->has_state()) {
|
if (this->temperature_sensor_->has_state()) {
|
||||||
|
@ -37,7 +37,6 @@ const LogString *adc_unit_to_str(adc_unit_t unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
|
|
||||||
// Check if another sensor already initialized this ADC unit
|
// Check if another sensor already initialized this ADC unit
|
||||||
if (ADCSensor::shared_adc_handles[this->adc_unit_] == nullptr) {
|
if (ADCSensor::shared_adc_handles[this->adc_unit_] == nullptr) {
|
||||||
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
|
adc_oneshot_unit_init_cfg_t init_config = {}; // Zero initialize
|
||||||
|
@ -17,7 +17,6 @@ namespace adc {
|
|||||||
static const char *const TAG = "adc.esp8266";
|
static const char *const TAG = "adc.esp8266";
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
|
|
||||||
#ifndef USE_ADC_SENSOR_VCC
|
#ifndef USE_ADC_SENSOR_VCC
|
||||||
this->pin_->setup();
|
this->pin_->setup();
|
||||||
#endif
|
#endif
|
||||||
|
@ -9,7 +9,6 @@ namespace adc {
|
|||||||
static const char *const TAG = "adc.libretiny";
|
static const char *const TAG = "adc.libretiny";
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
|
|
||||||
#ifndef USE_ADC_SENSOR_VCC
|
#ifndef USE_ADC_SENSOR_VCC
|
||||||
this->pin_->setup();
|
this->pin_->setup();
|
||||||
#endif // !USE_ADC_SENSOR_VCC
|
#endif // !USE_ADC_SENSOR_VCC
|
||||||
|
@ -14,7 +14,6 @@ namespace adc {
|
|||||||
static const char *const TAG = "adc.rp2040";
|
static const char *const TAG = "adc.rp2040";
|
||||||
|
|
||||||
void ADCSensor::setup() {
|
void ADCSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->get_name().c_str());
|
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
adc_init();
|
adc_init();
|
||||||
|
@ -8,10 +8,7 @@ static const char *const TAG = "adc128s102";
|
|||||||
|
|
||||||
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float ADC128S102::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
void ADC128S102::setup() {
|
void ADC128S102::setup() { this->spi_setup(); }
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->spi_setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ADC128S102::dump_config() {
|
void ADC128S102::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "ADC128S102:");
|
ESP_LOGCONFIG(TAG, "ADC128S102:");
|
||||||
|
@ -10,7 +10,6 @@ static const uint8_t ADS1115_REGISTER_CONVERSION = 0x00;
|
|||||||
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
static const uint8_t ADS1115_REGISTER_CONFIG = 0x01;
|
||||||
|
|
||||||
void ADS1115Component::setup() {
|
void ADS1115Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint16_t value;
|
uint16_t value;
|
||||||
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
if (!this->read_byte_16(ADS1115_REGISTER_CONVERSION, &value)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -9,7 +9,6 @@ static const char *const TAG = "ads1118";
|
|||||||
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
static const uint8_t ADS1118_DATA_RATE_860_SPS = 0b111;
|
||||||
|
|
||||||
void ADS1118::setup() {
|
void ADS1118::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
|
||||||
this->config_ = 0;
|
this->config_ = 0;
|
||||||
|
@ -24,8 +24,6 @@ static const uint16_t ZP_CURRENT = 0x0000;
|
|||||||
static const uint16_t ZP_DEFAULT = 0xFFFF;
|
static const uint16_t ZP_DEFAULT = 0xFFFF;
|
||||||
|
|
||||||
void AGS10Component::setup() {
|
void AGS10Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
auto version = this->read_version_();
|
auto version = this->read_version_();
|
||||||
if (version) {
|
if (version) {
|
||||||
ESP_LOGD(TAG, "AGS10 Sensor Version: 0x%02X", *version);
|
ESP_LOGD(TAG, "AGS10 Sensor Version: 0x%02X", *version);
|
||||||
@ -45,8 +43,6 @@ void AGS10Component::setup() {
|
|||||||
} else {
|
} else {
|
||||||
ESP_LOGE(TAG, "AGS10 Sensor Resistance: unknown");
|
ESP_LOGE(TAG, "AGS10 Sensor Resistance: unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Sensor initialized");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AGS10Component::update() {
|
void AGS10Component::update() {
|
||||||
|
@ -38,8 +38,6 @@ static const uint8_t AHT10_STATUS_BUSY = 0x80;
|
|||||||
static const float AHT10_DIVISOR = 1048576.0f; // 2^20, used for temperature and humidity calculations
|
static const float AHT10_DIVISOR = 1048576.0f; // 2^20, used for temperature and humidity calculations
|
||||||
|
|
||||||
void AHT10Component::setup() {
|
void AHT10Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
|
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
|
||||||
ESP_LOGE(TAG, "Reset failed");
|
ESP_LOGE(TAG, "Reset failed");
|
||||||
}
|
}
|
||||||
@ -80,8 +78,6 @@ void AHT10Component::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Initialization complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AHT10Component::restart_read_() {
|
void AHT10Component::restart_read_() {
|
||||||
|
@ -17,8 +17,6 @@ static const char *const TAG = "aic3204";
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AIC3204::setup() {
|
void AIC3204::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
// Set register page to 0
|
// Set register page to 0
|
||||||
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
|
ERROR_CHECK(this->write_byte(AIC3204_PAGE_CTRL, 0x00), "Set page 0 failed");
|
||||||
// Initiate SW reset (PLL is powered off as part of reset)
|
// Initiate SW reset (PLL is powered off as part of reset)
|
||||||
|
@ -90,8 +90,6 @@ bool AM2315C::convert_(uint8_t *data, float &humidity, float &temperature) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AM2315C::setup() {
|
void AM2315C::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
// get status
|
// get status
|
||||||
uint8_t status = 0;
|
uint8_t status = 0;
|
||||||
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
if (this->read(&status, 1) != i2c::ERROR_OK) {
|
||||||
|
@ -34,7 +34,6 @@ void AM2320Component::update() {
|
|||||||
this->status_clear_warning();
|
this->status_clear_warning();
|
||||||
}
|
}
|
||||||
void AM2320Component::setup() {
|
void AM2320Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t data[8];
|
uint8_t data[8];
|
||||||
data[0] = 0;
|
data[0] = 0;
|
||||||
data[1] = 4;
|
data[1] = 4;
|
||||||
|
@ -54,8 +54,6 @@ enum { // APDS9306 registers
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APDS9306::setup() {
|
void APDS9306::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
uint8_t id;
|
uint8_t id;
|
||||||
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
|
if (!this->read_byte(APDS9306_PART_ID, &id)) { // Part ID register
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
@ -86,8 +84,6 @@ void APDS9306::setup() {
|
|||||||
|
|
||||||
// Set to active mode
|
// Set to active mode
|
||||||
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
|
APDS9306_WRITE_BYTE(APDS9306_MAIN_CTRL, 0x02);
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, "APDS9306 setup complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void APDS9306::dump_config() {
|
void APDS9306::dump_config() {
|
||||||
|
@ -15,7 +15,6 @@ static const char *const TAG = "apds9960";
|
|||||||
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
|
#define APDS9960_WRITE_BYTE(reg, value) APDS9960_ERROR_CHECK(this->write_byte(reg, value));
|
||||||
|
|
||||||
void APDS9960::setup() {
|
void APDS9960::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t id;
|
uint8_t id;
|
||||||
if (!this->read_byte(0x92, &id)) { // ID register
|
if (!this->read_byte(0x92, &id)) { // ID register
|
||||||
this->error_code_ = COMMUNICATION_FAILED;
|
this->error_code_ = COMMUNICATION_FAILED;
|
||||||
|
@ -53,6 +53,8 @@ SERVICE_ARG_NATIVE_TYPES = {
|
|||||||
CONF_ENCRYPTION = "encryption"
|
CONF_ENCRYPTION = "encryption"
|
||||||
CONF_BATCH_DELAY = "batch_delay"
|
CONF_BATCH_DELAY = "batch_delay"
|
||||||
CONF_CUSTOM_SERVICES = "custom_services"
|
CONF_CUSTOM_SERVICES = "custom_services"
|
||||||
|
CONF_HOMEASSISTANT_SERVICES = "homeassistant_services"
|
||||||
|
CONF_HOMEASSISTANT_STATES = "homeassistant_states"
|
||||||
|
|
||||||
|
|
||||||
def validate_encryption_key(value):
|
def validate_encryption_key(value):
|
||||||
@ -118,6 +120,8 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_HOMEASSISTANT_SERVICES, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_HOMEASSISTANT_STATES, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||||
single=True
|
single=True
|
||||||
),
|
),
|
||||||
@ -146,6 +150,12 @@ async def to_code(config):
|
|||||||
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||||
cg.add_define("USE_API_SERVICES")
|
cg.add_define("USE_API_SERVICES")
|
||||||
|
|
||||||
|
if config[CONF_HOMEASSISTANT_SERVICES]:
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
|
|
||||||
|
if config[CONF_HOMEASSISTANT_STATES]:
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_STATES")
|
||||||
|
|
||||||
if actions := config.get(CONF_ACTIONS, []):
|
if actions := config.get(CONF_ACTIONS, []):
|
||||||
for conf in actions:
|
for conf in actions:
|
||||||
template_args = []
|
template_args = []
|
||||||
@ -235,6 +245,7 @@ HOMEASSISTANT_ACTION_ACTION_SCHEMA = cv.All(
|
|||||||
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
HOMEASSISTANT_ACTION_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
async def homeassistant_service_to_code(config, action_id, template_arg, args):
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
var = cg.new_Pvariable(action_id, template_arg, serv, False)
|
||||||
templ = await cg.templatable(config[CONF_ACTION], args, None)
|
templ = await cg.templatable(config[CONF_ACTION], args, None)
|
||||||
@ -278,6 +289,7 @@ HOMEASSISTANT_EVENT_ACTION_SCHEMA = cv.Schema(
|
|||||||
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
HOMEASSISTANT_EVENT_ACTION_SCHEMA,
|
||||||
)
|
)
|
||||||
async def homeassistant_event_to_code(config, action_id, template_arg, args):
|
async def homeassistant_event_to_code(config, action_id, template_arg, args):
|
||||||
|
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
|
||||||
serv = await cg.get_variable(config[CONF_ID])
|
serv = await cg.get_variable(config[CONF_ID])
|
||||||
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
var = cg.new_Pvariable(action_id, template_arg, serv, True)
|
||||||
templ = await cg.templatable(config[CONF_EVENT], args, None)
|
templ = await cg.templatable(config[CONF_EVENT], args, None)
|
||||||
@ -323,9 +335,10 @@ async def api_connected_to_code(config, condition_id, template_arg, args):
|
|||||||
|
|
||||||
|
|
||||||
def FILTER_SOURCE_FILES() -> list[str]:
|
def FILTER_SOURCE_FILES() -> list[str]:
|
||||||
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled
|
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled,
|
||||||
and user_services.cpp when no services are defined."""
|
user_services.cpp when no services are defined, and protocol-specific
|
||||||
files_to_filter = []
|
implementations based on encryption configuration."""
|
||||||
|
files_to_filter: list[str] = []
|
||||||
|
|
||||||
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
||||||
# This is a particularly large file that still needs to be opened and read
|
# This is a particularly large file that still needs to be opened and read
|
||||||
@ -341,4 +354,16 @@ def FILTER_SOURCE_FILES() -> list[str]:
|
|||||||
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
|
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
|
||||||
files_to_filter.append("user_services.cpp")
|
files_to_filter.append("user_services.cpp")
|
||||||
|
|
||||||
|
# Filter protocol-specific implementations based on encryption configuration
|
||||||
|
encryption_config = config.get(CONF_ENCRYPTION) if config else None
|
||||||
|
|
||||||
|
# If encryption is not configured at all, we only need plaintext
|
||||||
|
if encryption_config is None:
|
||||||
|
files_to_filter.append("api_frame_helper_noise.cpp")
|
||||||
|
# If encryption is configured with a key, we only need noise
|
||||||
|
elif encryption_config.get(CONF_KEY):
|
||||||
|
files_to_filter.append("api_frame_helper_plaintext.cpp")
|
||||||
|
# If encryption is configured but no key is provided, we need both
|
||||||
|
# (this allows a plaintext client to provide a noise key)
|
||||||
|
|
||||||
return files_to_filter
|
return files_to_filter
|
||||||
|
@ -203,7 +203,7 @@ message DeviceInfoResponse {
|
|||||||
option (id) = 10;
|
option (id) = 10;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
|
|
||||||
bool uses_password = 1;
|
bool uses_password = 1 [(field_ifdef) = "USE_API_PASSWORD"];
|
||||||
|
|
||||||
// The name of the node, given by "App.set_name()"
|
// The name of the node, given by "App.set_name()"
|
||||||
string name = 2;
|
string name = 2;
|
||||||
@ -230,14 +230,16 @@ message DeviceInfoResponse {
|
|||||||
|
|
||||||
uint32 webserver_port = 10 [(field_ifdef) = "USE_WEBSERVER"];
|
uint32 webserver_port = 10 [(field_ifdef) = "USE_WEBSERVER"];
|
||||||
|
|
||||||
uint32 legacy_bluetooth_proxy_version = 11 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
// Deprecated in API version 1.9
|
||||||
|
uint32 legacy_bluetooth_proxy_version = 11 [deprecated=true, (field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
||||||
uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
uint32 bluetooth_proxy_feature_flags = 15 [(field_ifdef) = "USE_BLUETOOTH_PROXY"];
|
||||||
|
|
||||||
string manufacturer = 12;
|
string manufacturer = 12;
|
||||||
|
|
||||||
string friendly_name = 13;
|
string friendly_name = 13;
|
||||||
|
|
||||||
uint32 legacy_voice_assistant_version = 14 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
|
// Deprecated in API version 1.10
|
||||||
|
uint32 legacy_voice_assistant_version = 14 [deprecated=true, (field_ifdef) = "USE_VOICE_ASSISTANT"];
|
||||||
uint32 voice_assistant_feature_flags = 17 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
|
uint32 voice_assistant_feature_flags = 17 [(field_ifdef) = "USE_VOICE_ASSISTANT"];
|
||||||
|
|
||||||
string suggested_area = 16 [(field_ifdef) = "USE_AREAS"];
|
string suggested_area = 16 [(field_ifdef) = "USE_AREAS"];
|
||||||
@ -337,7 +339,9 @@ message ListEntitiesCoverResponse {
|
|||||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated in API version 1.1
|
||||||
enum LegacyCoverState {
|
enum LegacyCoverState {
|
||||||
|
option deprecated = true;
|
||||||
LEGACY_COVER_STATE_OPEN = 0;
|
LEGACY_COVER_STATE_OPEN = 0;
|
||||||
LEGACY_COVER_STATE_CLOSED = 1;
|
LEGACY_COVER_STATE_CLOSED = 1;
|
||||||
}
|
}
|
||||||
@ -356,7 +360,8 @@ message CoverStateResponse {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
// legacy: state has been removed in 1.13
|
// legacy: state has been removed in 1.13
|
||||||
// clients/servers must still send/accept it until the next protocol change
|
// clients/servers must still send/accept it until the next protocol change
|
||||||
LegacyCoverState legacy_state = 2;
|
// Deprecated in API version 1.1
|
||||||
|
LegacyCoverState legacy_state = 2 [deprecated=true];
|
||||||
|
|
||||||
float position = 3;
|
float position = 3;
|
||||||
float tilt = 4;
|
float tilt = 4;
|
||||||
@ -364,7 +369,9 @@ message CoverStateResponse {
|
|||||||
uint32 device_id = 6 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 6 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated in API version 1.1
|
||||||
enum LegacyCoverCommand {
|
enum LegacyCoverCommand {
|
||||||
|
option deprecated = true;
|
||||||
LEGACY_COVER_COMMAND_OPEN = 0;
|
LEGACY_COVER_COMMAND_OPEN = 0;
|
||||||
LEGACY_COVER_COMMAND_CLOSE = 1;
|
LEGACY_COVER_COMMAND_CLOSE = 1;
|
||||||
LEGACY_COVER_COMMAND_STOP = 2;
|
LEGACY_COVER_COMMAND_STOP = 2;
|
||||||
@ -380,8 +387,10 @@ message CoverCommandRequest {
|
|||||||
|
|
||||||
// legacy: command has been removed in 1.13
|
// legacy: command has been removed in 1.13
|
||||||
// clients/servers must still send/accept it until the next protocol change
|
// clients/servers must still send/accept it until the next protocol change
|
||||||
bool has_legacy_command = 2;
|
// Deprecated in API version 1.1
|
||||||
LegacyCoverCommand legacy_command = 3;
|
bool has_legacy_command = 2 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.1
|
||||||
|
LegacyCoverCommand legacy_command = 3 [deprecated=true];
|
||||||
|
|
||||||
bool has_position = 4;
|
bool has_position = 4;
|
||||||
float position = 5;
|
float position = 5;
|
||||||
@ -413,7 +422,9 @@ message ListEntitiesFanResponse {
|
|||||||
repeated string supported_preset_modes = 12;
|
repeated string supported_preset_modes = 12;
|
||||||
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 13 [(field_ifdef) = "USE_DEVICES"];
|
||||||
}
|
}
|
||||||
|
// Deprecated in API version 1.6 - only used in deprecated fields
|
||||||
enum FanSpeed {
|
enum FanSpeed {
|
||||||
|
option deprecated = true;
|
||||||
FAN_SPEED_LOW = 0;
|
FAN_SPEED_LOW = 0;
|
||||||
FAN_SPEED_MEDIUM = 1;
|
FAN_SPEED_MEDIUM = 1;
|
||||||
FAN_SPEED_HIGH = 2;
|
FAN_SPEED_HIGH = 2;
|
||||||
@ -432,7 +443,8 @@ message FanStateResponse {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool state = 2;
|
bool state = 2;
|
||||||
bool oscillating = 3;
|
bool oscillating = 3;
|
||||||
FanSpeed speed = 4 [deprecated = true];
|
// Deprecated in API version 1.6
|
||||||
|
FanSpeed speed = 4 [deprecated=true];
|
||||||
FanDirection direction = 5;
|
FanDirection direction = 5;
|
||||||
int32 speed_level = 6;
|
int32 speed_level = 6;
|
||||||
string preset_mode = 7;
|
string preset_mode = 7;
|
||||||
@ -448,8 +460,10 @@ message FanCommandRequest {
|
|||||||
fixed32 key = 1;
|
fixed32 key = 1;
|
||||||
bool has_state = 2;
|
bool has_state = 2;
|
||||||
bool state = 3;
|
bool state = 3;
|
||||||
bool has_speed = 4 [deprecated = true];
|
// Deprecated in API version 1.6
|
||||||
FanSpeed speed = 5 [deprecated = true];
|
bool has_speed = 4 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.6
|
||||||
|
FanSpeed speed = 5 [deprecated=true];
|
||||||
bool has_oscillating = 6;
|
bool has_oscillating = 6;
|
||||||
bool oscillating = 7;
|
bool oscillating = 7;
|
||||||
bool has_direction = 8;
|
bool has_direction = 8;
|
||||||
@ -488,9 +502,13 @@ message ListEntitiesLightResponse {
|
|||||||
|
|
||||||
repeated ColorMode supported_color_modes = 12;
|
repeated ColorMode supported_color_modes = 12;
|
||||||
// next four supports_* are for legacy clients, newer clients should use color modes
|
// next four supports_* are for legacy clients, newer clients should use color modes
|
||||||
|
// Deprecated in API version 1.6
|
||||||
bool legacy_supports_brightness = 5 [deprecated=true];
|
bool legacy_supports_brightness = 5 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.6
|
||||||
bool legacy_supports_rgb = 6 [deprecated=true];
|
bool legacy_supports_rgb = 6 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.6
|
||||||
bool legacy_supports_white_value = 7 [deprecated=true];
|
bool legacy_supports_white_value = 7 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.6
|
||||||
bool legacy_supports_color_temperature = 8 [deprecated=true];
|
bool legacy_supports_color_temperature = 8 [deprecated=true];
|
||||||
float min_mireds = 9;
|
float min_mireds = 9;
|
||||||
float max_mireds = 10;
|
float max_mireds = 10;
|
||||||
@ -567,7 +585,9 @@ enum SensorStateClass {
|
|||||||
STATE_CLASS_TOTAL = 3;
|
STATE_CLASS_TOTAL = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated in API version 1.5
|
||||||
enum SensorLastResetType {
|
enum SensorLastResetType {
|
||||||
|
option deprecated = true;
|
||||||
LAST_RESET_NONE = 0;
|
LAST_RESET_NONE = 0;
|
||||||
LAST_RESET_NEVER = 1;
|
LAST_RESET_NEVER = 1;
|
||||||
LAST_RESET_AUTO = 2;
|
LAST_RESET_AUTO = 2;
|
||||||
@ -591,7 +611,8 @@ message ListEntitiesSensorResponse {
|
|||||||
string device_class = 9;
|
string device_class = 9;
|
||||||
SensorStateClass state_class = 10;
|
SensorStateClass state_class = 10;
|
||||||
// Last reset type removed in 2021.9.0
|
// Last reset type removed in 2021.9.0
|
||||||
SensorLastResetType legacy_last_reset_type = 11;
|
// Deprecated in API version 1.5
|
||||||
|
SensorLastResetType legacy_last_reset_type = 11 [deprecated=true];
|
||||||
bool disabled_by_default = 12;
|
bool disabled_by_default = 12;
|
||||||
EntityCategory entity_category = 13;
|
EntityCategory entity_category = 13;
|
||||||
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
|
uint32 device_id = 14 [(field_ifdef) = "USE_DEVICES"];
|
||||||
@ -711,7 +732,6 @@ message SubscribeLogsResponse {
|
|||||||
|
|
||||||
LogLevel level = 1;
|
LogLevel level = 1;
|
||||||
bytes message = 3;
|
bytes message = 3;
|
||||||
bool send_failed = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== NOISE ENCRYPTION ====================
|
// ==================== NOISE ENCRYPTION ====================
|
||||||
@ -735,17 +755,19 @@ message NoiseEncryptionSetKeyResponse {
|
|||||||
message SubscribeHomeassistantServicesRequest {
|
message SubscribeHomeassistantServicesRequest {
|
||||||
option (id) = 34;
|
option (id) = 34;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
|
||||||
}
|
}
|
||||||
|
|
||||||
message HomeassistantServiceMap {
|
message HomeassistantServiceMap {
|
||||||
string key = 1;
|
string key = 1;
|
||||||
string value = 2;
|
string value = 2 [(no_zero_copy) = true];
|
||||||
}
|
}
|
||||||
|
|
||||||
message HomeassistantServiceResponse {
|
message HomeassistantServiceResponse {
|
||||||
option (id) = 35;
|
option (id) = 35;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_SERVICES";
|
||||||
|
|
||||||
string service = 1;
|
string service = 1;
|
||||||
repeated HomeassistantServiceMap data = 2;
|
repeated HomeassistantServiceMap data = 2;
|
||||||
@ -761,11 +783,13 @@ message HomeassistantServiceResponse {
|
|||||||
message SubscribeHomeAssistantStatesRequest {
|
message SubscribeHomeAssistantStatesRequest {
|
||||||
option (id) = 38;
|
option (id) = 38;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||||
}
|
}
|
||||||
|
|
||||||
message SubscribeHomeAssistantStateResponse {
|
message SubscribeHomeAssistantStateResponse {
|
||||||
option (id) = 39;
|
option (id) = 39;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
string attribute = 2;
|
string attribute = 2;
|
||||||
bool once = 3;
|
bool once = 3;
|
||||||
@ -775,6 +799,7 @@ message HomeAssistantStateResponse {
|
|||||||
option (id) = 40;
|
option (id) = 40;
|
||||||
option (source) = SOURCE_CLIENT;
|
option (source) = SOURCE_CLIENT;
|
||||||
option (no_delay) = true;
|
option (no_delay) = true;
|
||||||
|
option (ifdef) = "USE_API_HOMEASSISTANT_STATES";
|
||||||
|
|
||||||
string entity_id = 1;
|
string entity_id = 1;
|
||||||
string state = 2;
|
string state = 2;
|
||||||
@ -947,7 +972,8 @@ message ListEntitiesClimateResponse {
|
|||||||
float visual_target_temperature_step = 10;
|
float visual_target_temperature_step = 10;
|
||||||
// for older peer versions - in new system this
|
// for older peer versions - in new system this
|
||||||
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
// is if CLIMATE_PRESET_AWAY exists is supported_presets
|
||||||
bool legacy_supports_away = 11;
|
// Deprecated in API version 1.5
|
||||||
|
bool legacy_supports_away = 11 [deprecated=true];
|
||||||
bool supports_action = 12;
|
bool supports_action = 12;
|
||||||
repeated ClimateFanMode supported_fan_modes = 13;
|
repeated ClimateFanMode supported_fan_modes = 13;
|
||||||
repeated ClimateSwingMode supported_swing_modes = 14;
|
repeated ClimateSwingMode supported_swing_modes = 14;
|
||||||
@ -978,7 +1004,8 @@ message ClimateStateResponse {
|
|||||||
float target_temperature_low = 5;
|
float target_temperature_low = 5;
|
||||||
float target_temperature_high = 6;
|
float target_temperature_high = 6;
|
||||||
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
|
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
|
||||||
bool unused_legacy_away = 7;
|
// Deprecated in API version 1.5
|
||||||
|
bool unused_legacy_away = 7 [deprecated=true];
|
||||||
ClimateAction action = 8;
|
ClimateAction action = 8;
|
||||||
ClimateFanMode fan_mode = 9;
|
ClimateFanMode fan_mode = 9;
|
||||||
ClimateSwingMode swing_mode = 10;
|
ClimateSwingMode swing_mode = 10;
|
||||||
@ -1006,8 +1033,10 @@ message ClimateCommandRequest {
|
|||||||
bool has_target_temperature_high = 8;
|
bool has_target_temperature_high = 8;
|
||||||
float target_temperature_high = 9;
|
float target_temperature_high = 9;
|
||||||
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
|
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
|
||||||
bool unused_has_legacy_away = 10;
|
// Deprecated in API version 1.5
|
||||||
bool unused_legacy_away = 11;
|
bool unused_has_legacy_away = 10 [deprecated=true];
|
||||||
|
// Deprecated in API version 1.5
|
||||||
|
bool unused_legacy_away = 11 [deprecated=true];
|
||||||
bool has_fan_mode = 12;
|
bool has_fan_mode = 12;
|
||||||
ClimateFanMode fan_mode = 13;
|
ClimateFanMode fan_mode = 13;
|
||||||
bool has_swing_mode = 14;
|
bool has_swing_mode = 14;
|
||||||
@ -1354,12 +1383,17 @@ message SubscribeBluetoothLEAdvertisementsRequest {
|
|||||||
uint32 flags = 1;
|
uint32 flags = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated - only used by deprecated BluetoothLEAdvertisementResponse
|
||||||
message BluetoothServiceData {
|
message BluetoothServiceData {
|
||||||
|
option deprecated = true;
|
||||||
string uuid = 1;
|
string uuid = 1;
|
||||||
repeated uint32 legacy_data = 2 [deprecated = true]; // Removed in api version 1.7
|
// Deprecated in API version 1.7
|
||||||
|
repeated uint32 legacy_data = 2 [deprecated=true]; // Removed in api version 1.7
|
||||||
bytes data = 3; // Added in api version 1.7
|
bytes data = 3; // Added in api version 1.7
|
||||||
}
|
}
|
||||||
|
// Removed in ESPHome 2025.8.0 - use BluetoothLERawAdvertisementsResponse instead
|
||||||
message BluetoothLEAdvertisementResponse {
|
message BluetoothLEAdvertisementResponse {
|
||||||
|
option deprecated = true;
|
||||||
option (id) = 67;
|
option (id) = 67;
|
||||||
option (source) = SOURCE_SERVER;
|
option (source) = SOURCE_SERVER;
|
||||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
@ -1381,7 +1415,7 @@ message BluetoothLERawAdvertisement {
|
|||||||
sint32 rssi = 2;
|
sint32 rssi = 2;
|
||||||
uint32 address_type = 3;
|
uint32 address_type = 3;
|
||||||
|
|
||||||
bytes data = 4;
|
bytes data = 4 [(fixed_array_size) = 62];
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothLERawAdvertisementsResponse {
|
message BluetoothLERawAdvertisementsResponse {
|
||||||
@ -1434,19 +1468,19 @@ message BluetoothGATTGetServicesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTDescriptor {
|
message BluetoothGATTDescriptor {
|
||||||
repeated uint64 uuid = 1;
|
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
|
||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTCharacteristic {
|
message BluetoothGATTCharacteristic {
|
||||||
repeated uint64 uuid = 1;
|
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
|
||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
uint32 properties = 3;
|
uint32 properties = 3;
|
||||||
repeated BluetoothGATTDescriptor descriptors = 4;
|
repeated BluetoothGATTDescriptor descriptors = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTService {
|
message BluetoothGATTService {
|
||||||
repeated uint64 uuid = 1;
|
repeated uint64 uuid = 1 [(fixed_array_size) = 2];
|
||||||
uint32 handle = 2;
|
uint32 handle = 2;
|
||||||
repeated BluetoothGATTCharacteristic characteristics = 3;
|
repeated BluetoothGATTCharacteristic characteristics = 3;
|
||||||
}
|
}
|
||||||
@ -1457,7 +1491,7 @@ message BluetoothGATTGetServicesResponse {
|
|||||||
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
option (ifdef) = "USE_BLUETOOTH_PROXY";
|
||||||
|
|
||||||
uint64 address = 1;
|
uint64 address = 1;
|
||||||
repeated BluetoothGATTService services = 2;
|
repeated BluetoothGATTService services = 2 [(fixed_array_size) = 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
message BluetoothGATTGetServicesDoneResponse {
|
message BluetoothGATTGetServicesDoneResponse {
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
#include "api_frame_helper_noise.h"
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_PLAINTEXT
|
||||||
|
#include "api_frame_helper_plaintext.h"
|
||||||
|
#endif
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -25,8 +31,7 @@
|
|||||||
#include "esphome/components/voice_assistant/voice_assistant.h"
|
#include "esphome/components/voice_assistant/voice_assistant.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
// Read a maximum of 5 messages per loop iteration to prevent starving other components.
|
// Read a maximum of 5 messages per loop iteration to prevent starving other components.
|
||||||
// This is a balance between API responsiveness and allowing other components to run.
|
// This is a balance between API responsiveness and allowing other components to run.
|
||||||
@ -79,14 +84,16 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
|
|||||||
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
|
||||||
auto noise_ctx = parent->get_noise_ctx();
|
auto noise_ctx = parent->get_noise_ctx();
|
||||||
if (noise_ctx->has_psk()) {
|
if (noise_ctx->has_psk()) {
|
||||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
|
this->helper_ =
|
||||||
|
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
|
||||||
} else {
|
} else {
|
||||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
|
||||||
}
|
}
|
||||||
#elif defined(USE_API_PLAINTEXT)
|
#elif defined(USE_API_PLAINTEXT)
|
||||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
|
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
|
||||||
#elif defined(USE_API_NOISE)
|
#elif defined(USE_API_NOISE)
|
||||||
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
|
this->helper_ = std::unique_ptr<APIFrameHelper>{
|
||||||
|
new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
|
||||||
#else
|
#else
|
||||||
#error "No frame helper defined"
|
#error "No frame helper defined"
|
||||||
#endif
|
#endif
|
||||||
@ -109,9 +116,8 @@ void APIConnection::start() {
|
|||||||
errno);
|
errno);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->client_info_ = helper_->getpeername();
|
this->client_info_.peername = helper_->getpeername();
|
||||||
this->client_peername_ = this->client_info_;
|
this->client_info_.name = this->client_info_.peername;
|
||||||
this->helper_->set_log_info(this->client_info_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
APIConnection::~APIConnection() {
|
APIConnection::~APIConnection() {
|
||||||
@ -218,24 +224,16 @@ void APIConnection::loop() {
|
|||||||
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) {
|
||||||
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available());
|
||||||
bool done = this->image_reader_->available() == to_send;
|
bool done = this->image_reader_->available() == to_send;
|
||||||
uint32_t msg_size = 0;
|
|
||||||
ProtoSize::add_fixed_field<4>(msg_size, 1, true);
|
|
||||||
// partial message size calculated manually since its a special case
|
|
||||||
// 1 for the data field, varint for the data size, and the data itself
|
|
||||||
msg_size += 1 + ProtoSize::varint(to_send) + to_send;
|
|
||||||
ProtoSize::add_bool_field(msg_size, 1, done);
|
|
||||||
|
|
||||||
auto buffer = this->create_buffer(msg_size);
|
CameraImageResponse msg;
|
||||||
// fixed32 key = 1;
|
msg.key = camera::Camera::instance()->get_object_id_hash();
|
||||||
buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash());
|
msg.set_data(this->image_reader_->peek_data_buffer(), to_send);
|
||||||
// bytes data = 2;
|
msg.done = done;
|
||||||
buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send);
|
#ifdef USE_DEVICES
|
||||||
// bool done = 3;
|
msg.device_id = camera::Camera::instance()->get_device_id();
|
||||||
buffer.encode_bool(3, done);
|
#endif
|
||||||
|
|
||||||
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
|
if (this->send_message_(msg, CameraImageResponse::MESSAGE_TYPE)) {
|
||||||
|
|
||||||
if (success) {
|
|
||||||
this->image_reader_->consume_data(to_send);
|
this->image_reader_->consume_data(to_send);
|
||||||
if (done) {
|
if (done) {
|
||||||
this->image_reader_->return_image();
|
this->image_reader_->return_image();
|
||||||
@ -244,31 +242,21 @@ void APIConnection::loop() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
if (state_subs_at_ >= 0) {
|
if (state_subs_at_ >= 0) {
|
||||||
const auto &subs = this->parent_->get_state_subs();
|
this->process_state_subscriptions_();
|
||||||
if (state_subs_at_ < static_cast<int>(subs.size())) {
|
|
||||||
auto &it = subs[state_subs_at_];
|
|
||||||
SubscribeHomeAssistantStateResponse resp;
|
|
||||||
resp.entity_id = it.entity_id;
|
|
||||||
resp.attribute = it.attribute.value();
|
|
||||||
resp.once = it.once;
|
|
||||||
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
|
|
||||||
state_subs_at_++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state_subs_at_ = -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) {
|
bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
||||||
// remote initiated disconnect_client
|
// remote initiated disconnect_client
|
||||||
// don't close yet, we still need to send the disconnect response
|
// don't close yet, we still need to send the disconnect response
|
||||||
// close will happen on next loop
|
// close will happen on next loop
|
||||||
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s disconnected", this->get_client_combined_info().c_str());
|
||||||
this->flags_.next_close = true;
|
this->flags_.next_close = true;
|
||||||
DisconnectResponse resp;
|
DisconnectResponse resp;
|
||||||
return resp;
|
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
|
||||||
this->helper_->close();
|
this->helper_->close();
|
||||||
@ -345,7 +333,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
|
auto *binary_sensor = static_cast<binary_sensor::BinarySensor *>(entity);
|
||||||
ListEntitiesBinarySensorResponse msg;
|
ListEntitiesBinarySensorResponse msg;
|
||||||
msg.device_class = binary_sensor->get_device_class();
|
msg.set_device_class(binary_sensor->get_device_class_ref());
|
||||||
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
|
||||||
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
|
return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn,
|
||||||
remaining_size, is_single);
|
remaining_size, is_single);
|
||||||
@ -362,8 +350,6 @@ uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *
|
|||||||
auto *cover = static_cast<cover::Cover *>(entity);
|
auto *cover = static_cast<cover::Cover *>(entity);
|
||||||
CoverStateResponse msg;
|
CoverStateResponse msg;
|
||||||
auto traits = cover->get_traits();
|
auto traits = cover->get_traits();
|
||||||
msg.legacy_state =
|
|
||||||
(cover->position == cover::COVER_OPEN) ? enums::LEGACY_COVER_STATE_OPEN : enums::LEGACY_COVER_STATE_CLOSED;
|
|
||||||
msg.position = cover->position;
|
msg.position = cover->position;
|
||||||
if (traits.get_supports_tilt())
|
if (traits.get_supports_tilt())
|
||||||
msg.tilt = cover->tilt;
|
msg.tilt = cover->tilt;
|
||||||
@ -379,25 +365,12 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
|
|||||||
msg.supports_position = traits.get_supports_position();
|
msg.supports_position = traits.get_supports_position();
|
||||||
msg.supports_tilt = traits.get_supports_tilt();
|
msg.supports_tilt = traits.get_supports_tilt();
|
||||||
msg.supports_stop = traits.get_supports_stop();
|
msg.supports_stop = traits.get_supports_stop();
|
||||||
msg.device_class = cover->get_device_class();
|
msg.set_device_class(cover->get_device_class_ref());
|
||||||
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||||
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
|
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover)
|
||||||
if (msg.has_legacy_command) {
|
|
||||||
switch (msg.legacy_command) {
|
|
||||||
case enums::LEGACY_COVER_COMMAND_OPEN:
|
|
||||||
call.set_command_open();
|
|
||||||
break;
|
|
||||||
case enums::LEGACY_COVER_COMMAND_CLOSE:
|
|
||||||
call.set_command_close();
|
|
||||||
break;
|
|
||||||
case enums::LEGACY_COVER_COMMAND_STOP:
|
|
||||||
call.set_command_stop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.has_position)
|
if (msg.has_position)
|
||||||
call.set_position(msg.position);
|
call.set_position(msg.position);
|
||||||
if (msg.has_tilt)
|
if (msg.has_tilt)
|
||||||
@ -427,7 +400,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
|||||||
if (traits.supports_direction())
|
if (traits.supports_direction())
|
||||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||||
if (traits.supports_preset_modes())
|
if (traits.supports_preset_modes())
|
||||||
msg.preset_mode = fan->preset_mode;
|
msg.set_preset_mode(StringRef(fan->preset_mode));
|
||||||
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -484,8 +457,11 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
|||||||
resp.color_temperature = values.get_color_temperature();
|
resp.color_temperature = values.get_color_temperature();
|
||||||
resp.cold_white = values.get_cold_white();
|
resp.cold_white = values.get_cold_white();
|
||||||
resp.warm_white = values.get_warm_white();
|
resp.warm_white = values.get_warm_white();
|
||||||
if (light->supports_effects())
|
if (light->supports_effects()) {
|
||||||
resp.effect = light->get_effect_name();
|
// get_effect_name() returns temporary std::string - must store it
|
||||||
|
std::string effect_name = light->get_effect_name();
|
||||||
|
resp.set_effect(StringRef(effect_name));
|
||||||
|
}
|
||||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
@ -495,14 +471,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
|||||||
auto traits = light->get_traits();
|
auto traits = light->get_traits();
|
||||||
for (auto mode : traits.get_supported_color_modes())
|
for (auto mode : traits.get_supported_color_modes())
|
||||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||||
msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
|
if (traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||||
msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
|
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE)) {
|
||||||
msg.legacy_supports_white_value =
|
|
||||||
msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
|
|
||||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
|
|
||||||
msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
|
||||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
|
|
||||||
if (msg.legacy_supports_color_temperature) {
|
|
||||||
msg.min_mireds = traits.get_min_mireds();
|
msg.min_mireds = traits.get_min_mireds();
|
||||||
msg.max_mireds = traits.get_max_mireds();
|
msg.max_mireds = traits.get_max_mireds();
|
||||||
}
|
}
|
||||||
@ -567,10 +537,10 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *sensor = static_cast<sensor::Sensor *>(entity);
|
auto *sensor = static_cast<sensor::Sensor *>(entity);
|
||||||
ListEntitiesSensorResponse msg;
|
ListEntitiesSensorResponse msg;
|
||||||
msg.unit_of_measurement = sensor->get_unit_of_measurement();
|
msg.set_unit_of_measurement(sensor->get_unit_of_measurement_ref());
|
||||||
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
msg.accuracy_decimals = sensor->get_accuracy_decimals();
|
||||||
msg.force_update = sensor->get_force_update();
|
msg.force_update = sensor->get_force_update();
|
||||||
msg.device_class = sensor->get_device_class();
|
msg.set_device_class(sensor->get_device_class_ref());
|
||||||
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
|
msg.state_class = static_cast<enums::SensorStateClass>(sensor->get_state_class());
|
||||||
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
@ -597,7 +567,7 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
|
|||||||
auto *a_switch = static_cast<switch_::Switch *>(entity);
|
auto *a_switch = static_cast<switch_::Switch *>(entity);
|
||||||
ListEntitiesSwitchResponse msg;
|
ListEntitiesSwitchResponse msg;
|
||||||
msg.assumed_state = a_switch->assumed_state();
|
msg.assumed_state = a_switch->assumed_state();
|
||||||
msg.device_class = a_switch->get_device_class();
|
msg.set_device_class(a_switch->get_device_class_ref());
|
||||||
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@ -622,7 +592,7 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
||||||
TextSensorStateResponse resp;
|
TextSensorStateResponse resp;
|
||||||
resp.state = text_sensor->state;
|
resp.set_state(StringRef(text_sensor->state));
|
||||||
resp.missing_state = !text_sensor->has_state();
|
resp.missing_state = !text_sensor->has_state();
|
||||||
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_state(text_sensor, resp, TextSensorStateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
@ -631,7 +601,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
auto *text_sensor = static_cast<text_sensor::TextSensor *>(entity);
|
||||||
ListEntitiesTextSensorResponse msg;
|
ListEntitiesTextSensorResponse msg;
|
||||||
msg.device_class = text_sensor->get_device_class();
|
msg.set_device_class(text_sensor->get_device_class_ref());
|
||||||
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
|
return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn,
|
||||||
remaining_size, is_single);
|
remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@ -659,13 +629,15 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
|||||||
}
|
}
|
||||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value())
|
if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) {
|
||||||
resp.custom_fan_mode = climate->custom_fan_mode.value();
|
resp.set_custom_fan_mode(StringRef(climate->custom_fan_mode.value()));
|
||||||
|
}
|
||||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||||
}
|
}
|
||||||
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
|
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) {
|
||||||
resp.custom_preset = climate->custom_preset.value();
|
resp.set_custom_preset(StringRef(climate->custom_preset.value()));
|
||||||
|
}
|
||||||
if (traits.get_supports_swing_modes())
|
if (traits.get_supports_swing_modes())
|
||||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||||
if (traits.get_supports_current_humidity())
|
if (traits.get_supports_current_humidity())
|
||||||
@ -692,7 +664,6 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
|
|||||||
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
msg.visual_current_temperature_step = traits.get_visual_current_temperature_step();
|
||||||
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
msg.visual_min_humidity = traits.get_visual_min_humidity();
|
||||||
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
msg.visual_max_humidity = traits.get_visual_max_humidity();
|
||||||
msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY);
|
|
||||||
msg.supports_action = traits.get_supports_action();
|
msg.supports_action = traits.get_supports_action();
|
||||||
for (auto fan_mode : traits.get_supported_fan_modes())
|
for (auto fan_mode : traits.get_supported_fan_modes())
|
||||||
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
msg.supported_fan_modes.push_back(static_cast<enums::ClimateFanMode>(fan_mode));
|
||||||
@ -752,9 +723,9 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *number = static_cast<number::Number *>(entity);
|
auto *number = static_cast<number::Number *>(entity);
|
||||||
ListEntitiesNumberResponse msg;
|
ListEntitiesNumberResponse msg;
|
||||||
msg.unit_of_measurement = number->traits.get_unit_of_measurement();
|
msg.set_unit_of_measurement(number->traits.get_unit_of_measurement_ref());
|
||||||
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
|
msg.mode = static_cast<enums::NumberMode>(number->traits.get_mode());
|
||||||
msg.device_class = number->traits.get_device_class();
|
msg.set_device_class(number->traits.get_device_class_ref());
|
||||||
msg.min_value = number->traits.get_min_value();
|
msg.min_value = number->traits.get_min_value();
|
||||||
msg.max_value = number->traits.get_max_value();
|
msg.max_value = number->traits.get_max_value();
|
||||||
msg.step = number->traits.get_step();
|
msg.step = number->traits.get_step();
|
||||||
@ -867,7 +838,7 @@ uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *c
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *text = static_cast<text::Text *>(entity);
|
auto *text = static_cast<text::Text *>(entity);
|
||||||
TextStateResponse resp;
|
TextStateResponse resp;
|
||||||
resp.state = text->state;
|
resp.set_state(StringRef(text->state));
|
||||||
resp.missing_state = !text->has_state();
|
resp.missing_state = !text->has_state();
|
||||||
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(text, resp, TextStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@ -879,7 +850,7 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
|
|||||||
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
|
msg.mode = static_cast<enums::TextMode>(text->traits.get_mode());
|
||||||
msg.min_length = text->traits.get_min_length();
|
msg.min_length = text->traits.get_min_length();
|
||||||
msg.max_length = text->traits.get_max_length();
|
msg.max_length = text->traits.get_max_length();
|
||||||
msg.pattern = text->traits.get_pattern();
|
msg.set_pattern(text->traits.get_pattern_ref());
|
||||||
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(text, msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@ -900,7 +871,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *select = static_cast<select::Select *>(entity);
|
auto *select = static_cast<select::Select *>(entity);
|
||||||
SelectStateResponse resp;
|
SelectStateResponse resp;
|
||||||
resp.state = select->state;
|
resp.set_state(StringRef(select->state));
|
||||||
resp.missing_state = !select->has_state();
|
resp.missing_state = !select->has_state();
|
||||||
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@ -926,7 +897,7 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *button = static_cast<button::Button *>(entity);
|
auto *button = static_cast<button::Button *>(entity);
|
||||||
ListEntitiesButtonResponse msg;
|
ListEntitiesButtonResponse msg;
|
||||||
msg.device_class = button->get_device_class();
|
msg.set_device_class(button->get_device_class_ref());
|
||||||
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@ -995,7 +966,7 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
|
|||||||
auto *valve = static_cast<valve::Valve *>(entity);
|
auto *valve = static_cast<valve::Valve *>(entity);
|
||||||
ListEntitiesValveResponse msg;
|
ListEntitiesValveResponse msg;
|
||||||
auto traits = valve->get_traits();
|
auto traits = valve->get_traits();
|
||||||
msg.device_class = valve->get_device_class();
|
msg.set_device_class(valve->get_device_class_ref());
|
||||||
msg.assumed_state = traits.get_is_assumed_state();
|
msg.assumed_state = traits.get_is_assumed_state();
|
||||||
msg.supports_position = traits.get_supports_position();
|
msg.supports_position = traits.get_supports_position();
|
||||||
msg.supports_stop = traits.get_supports_stop();
|
msg.supports_stop = traits.get_supports_stop();
|
||||||
@ -1037,13 +1008,13 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
|
|||||||
auto traits = media_player->get_traits();
|
auto traits = media_player->get_traits();
|
||||||
msg.supports_pause = traits.get_supports_pause();
|
msg.supports_pause = traits.get_supports_pause();
|
||||||
for (auto &supported_format : traits.get_supported_formats()) {
|
for (auto &supported_format : traits.get_supported_formats()) {
|
||||||
MediaPlayerSupportedFormat media_format;
|
msg.supported_formats.emplace_back();
|
||||||
media_format.format = supported_format.format;
|
auto &media_format = msg.supported_formats.back();
|
||||||
|
media_format.set_format(StringRef(supported_format.format));
|
||||||
media_format.sample_rate = supported_format.sample_rate;
|
media_format.sample_rate = supported_format.sample_rate;
|
||||||
media_format.num_channels = supported_format.num_channels;
|
media_format.num_channels = supported_format.num_channels;
|
||||||
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
|
media_format.purpose = static_cast<enums::MediaPlayerFormatPurpose>(supported_format.purpose);
|
||||||
media_format.sample_bytes = supported_format.sample_bytes;
|
media_format.sample_bytes = supported_format.sample_bytes;
|
||||||
msg.supported_formats.push_back(media_format);
|
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
|
return fill_and_encode_entity_info(media_player, msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn,
|
||||||
remaining_size, is_single);
|
remaining_size, is_single);
|
||||||
@ -1106,6 +1077,12 @@ void APIConnection::on_get_time_response(const GetTimeResponse &value) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool APIConnection::send_get_time_response(const GetTimeRequest &msg) {
|
||||||
|
GetTimeResponse resp;
|
||||||
|
resp.epoch_seconds = ::time(nullptr);
|
||||||
|
return this->send_message(resp, GetTimeResponse::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
|
bluetooth_proxy::global_bluetooth_proxy->subscribe_api_connection(this, msg.flags);
|
||||||
@ -1113,21 +1090,6 @@ void APIConnection::subscribe_bluetooth_le_advertisements(const SubscribeBluetoo
|
|||||||
void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
void APIConnection::unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) {
|
||||||
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this);
|
||||||
}
|
}
|
||||||
bool APIConnection::send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg) {
|
|
||||||
if (this->client_api_version_major_ < 1 || this->client_api_version_minor_ < 7) {
|
|
||||||
BluetoothLEAdvertisementResponse resp = msg;
|
|
||||||
for (auto &service : resp.service_data) {
|
|
||||||
service.legacy_data.assign(service.data.begin(), service.data.end());
|
|
||||||
service.data.clear();
|
|
||||||
}
|
|
||||||
for (auto &manufacturer_data : resp.manufacturer_data) {
|
|
||||||
manufacturer_data.legacy_data.assign(manufacturer_data.data.begin(), manufacturer_data.data.end());
|
|
||||||
manufacturer_data.data.clear();
|
|
||||||
}
|
|
||||||
return this->send_message(resp, BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
return this->send_message(msg, BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
void APIConnection::bluetooth_device_request(const BluetoothDeviceRequest &msg) {
|
||||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
|
bluetooth_proxy::global_bluetooth_proxy->bluetooth_device_request(msg);
|
||||||
}
|
}
|
||||||
@ -1151,12 +1113,12 @@ void APIConnection::bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg)
|
|||||||
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
|
bluetooth_proxy::global_bluetooth_proxy->bluetooth_gatt_notify(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_free(
|
bool APIConnection::send_subscribe_bluetooth_connections_free_response(
|
||||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||||
BluetoothConnectionsFreeResponse resp;
|
BluetoothConnectionsFreeResponse resp;
|
||||||
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
|
resp.free = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_free();
|
||||||
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
resp.limit = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_connections_limit();
|
||||||
return resp;
|
return this->send_message(resp, BluetoothConnectionsFreeResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
|
void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) {
|
||||||
@ -1217,28 +1179,27 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
|
bool APIConnection::send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) {
|
||||||
const VoiceAssistantConfigurationRequest &msg) {
|
|
||||||
VoiceAssistantConfigurationResponse resp;
|
VoiceAssistantConfigurationResponse resp;
|
||||||
if (!this->check_voice_assistant_api_connection_()) {
|
if (!this->check_voice_assistant_api_connection_()) {
|
||||||
return resp;
|
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &config = voice_assistant::global_voice_assistant->get_configuration();
|
auto &config = voice_assistant::global_voice_assistant->get_configuration();
|
||||||
for (auto &wake_word : config.available_wake_words) {
|
for (auto &wake_word : config.available_wake_words) {
|
||||||
VoiceAssistantWakeWord resp_wake_word;
|
resp.available_wake_words.emplace_back();
|
||||||
resp_wake_word.id = wake_word.id;
|
auto &resp_wake_word = resp.available_wake_words.back();
|
||||||
resp_wake_word.wake_word = wake_word.wake_word;
|
resp_wake_word.set_id(StringRef(wake_word.id));
|
||||||
|
resp_wake_word.set_wake_word(StringRef(wake_word.wake_word));
|
||||||
for (const auto &lang : wake_word.trained_languages) {
|
for (const auto &lang : wake_word.trained_languages) {
|
||||||
resp_wake_word.trained_languages.push_back(lang);
|
resp_wake_word.trained_languages.push_back(lang);
|
||||||
}
|
}
|
||||||
resp.available_wake_words.push_back(std::move(resp_wake_word));
|
|
||||||
}
|
}
|
||||||
for (auto &wake_word_id : config.active_wake_words) {
|
for (auto &wake_word_id : config.active_wake_words) {
|
||||||
resp.active_wake_words.push_back(wake_word_id);
|
resp.active_wake_words.push_back(wake_word_id);
|
||||||
}
|
}
|
||||||
resp.max_active_wake_words = config.max_active_wake_words;
|
resp.max_active_wake_words = config.max_active_wake_words;
|
||||||
return resp;
|
return this->send_message(resp, VoiceAssistantConfigurationResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
|
||||||
@ -1311,7 +1272,7 @@ void APIConnection::send_event(event::Event *event, const std::string &event_typ
|
|||||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single) {
|
uint32_t remaining_size, bool is_single) {
|
||||||
EventResponse resp;
|
EventResponse resp;
|
||||||
resp.event_type = event_type;
|
resp.set_event_type(StringRef(event_type));
|
||||||
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1319,7 +1280,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *event = static_cast<event::Event *>(entity);
|
auto *event = static_cast<event::Event *>(entity);
|
||||||
ListEntitiesEventResponse msg;
|
ListEntitiesEventResponse msg;
|
||||||
msg.device_class = event->get_device_class();
|
msg.set_device_class(event->get_device_class_ref());
|
||||||
for (const auto &event_type : event->get_event_types())
|
for (const auto &event_type : event->get_event_types())
|
||||||
msg.event_types.push_back(event_type);
|
msg.event_types.push_back(event_type);
|
||||||
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
@ -1343,11 +1304,11 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection
|
|||||||
resp.has_progress = true;
|
resp.has_progress = true;
|
||||||
resp.progress = update->update_info.progress;
|
resp.progress = update->update_info.progress;
|
||||||
}
|
}
|
||||||
resp.current_version = update->update_info.current_version;
|
resp.set_current_version(StringRef(update->update_info.current_version));
|
||||||
resp.latest_version = update->update_info.latest_version;
|
resp.set_latest_version(StringRef(update->update_info.latest_version));
|
||||||
resp.title = update->update_info.title;
|
resp.set_title(StringRef(update->update_info.title));
|
||||||
resp.release_summary = update->update_info.summary;
|
resp.set_release_summary(StringRef(update->update_info.summary));
|
||||||
resp.release_url = update->update_info.release_url;
|
resp.set_release_url(StringRef(update->update_info.release_url));
|
||||||
}
|
}
|
||||||
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return fill_and_encode_entity_state(update, resp, UpdateStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
@ -1355,7 +1316,7 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
|
|||||||
bool is_single) {
|
bool is_single) {
|
||||||
auto *update = static_cast<update::UpdateEntity *>(entity);
|
auto *update = static_cast<update::UpdateEntity *>(entity);
|
||||||
ListEntitiesUpdateResponse msg;
|
ListEntitiesUpdateResponse msg;
|
||||||
msg.device_class = update->get_device_class();
|
msg.set_device_class(update->get_device_class_ref());
|
||||||
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
|
return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||||
is_single);
|
is_single);
|
||||||
}
|
}
|
||||||
@ -1380,26 +1341,10 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
|
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
|
||||||
// Pre-calculate message size to avoid reallocations
|
SubscribeLogsResponse msg;
|
||||||
uint32_t msg_size = 0;
|
msg.level = static_cast<enums::LogLevel>(level);
|
||||||
|
msg.set_message(reinterpret_cast<const uint8_t *>(line), message_len);
|
||||||
// Add size for level field (field ID 1, varint type)
|
return this->send_message_(msg, SubscribeLogsResponse::MESSAGE_TYPE);
|
||||||
// 1 byte for field tag + size of the level varint
|
|
||||||
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(level));
|
|
||||||
|
|
||||||
// Add size for string field (field ID 3, string type)
|
|
||||||
// 1 byte for field tag + size of length varint + string length
|
|
||||||
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len;
|
|
||||||
|
|
||||||
// Create a pre-sized buffer
|
|
||||||
auto buffer = this->create_buffer(msg_size);
|
|
||||||
|
|
||||||
// Encode the message (SubscribeLogsResponse)
|
|
||||||
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
|
|
||||||
buffer.encode_string(3, line, message_len); // string message = 3
|
|
||||||
|
|
||||||
// SubscribeLogsResponse - 29
|
|
||||||
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::complete_authentication_() {
|
void APIConnection::complete_authentication_() {
|
||||||
@ -1411,7 +1356,7 @@ void APIConnection::complete_authentication_() {
|
|||||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
||||||
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s connected", this->get_client_combined_info().c_str());
|
||||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_);
|
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||||
@ -1420,20 +1365,21 @@ void APIConnection::complete_authentication_() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
||||||
this->client_info_ = msg.client_info;
|
this->client_info_.name = msg.client_info;
|
||||||
this->client_peername_ = this->helper_->getpeername();
|
this->client_info_.peername = this->helper_->getpeername();
|
||||||
this->helper_->set_log_info(this->get_client_combined_info());
|
|
||||||
this->client_api_version_major_ = msg.api_version_major;
|
this->client_api_version_major_ = msg.api_version_major;
|
||||||
this->client_api_version_minor_ = msg.api_version_minor;
|
this->client_api_version_minor_ = msg.api_version_minor;
|
||||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(),
|
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
|
||||||
this->client_peername_.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
|
this->client_info_.peername.c_str(), this->client_api_version_major_, this->client_api_version_minor_);
|
||||||
|
|
||||||
HelloResponse resp;
|
HelloResponse resp;
|
||||||
resp.api_version_major = 1;
|
resp.api_version_major = 1;
|
||||||
resp.api_version_minor = 10;
|
resp.api_version_minor = 10;
|
||||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
// Temporary string for concatenation - will be valid during send_message call
|
||||||
resp.name = App.get_name();
|
std::string server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||||
|
resp.set_server_info(StringRef(server_info));
|
||||||
|
resp.set_name(StringRef(App.get_name()));
|
||||||
|
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
// Password required - wait for authentication
|
// Password required - wait for authentication
|
||||||
@ -1443,9 +1389,9 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
|||||||
this->complete_authentication_();
|
this->complete_authentication_();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return resp;
|
return this->send_message(resp, HelloResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
bool APIConnection::send_connect_response(const ConnectRequest &msg) {
|
||||||
bool correct = true;
|
bool correct = true;
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
correct = this->parent_->check_password(msg.password);
|
correct = this->parent_->check_password(msg.password);
|
||||||
@ -1457,54 +1403,73 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
|||||||
if (correct) {
|
if (correct) {
|
||||||
this->complete_authentication_();
|
this->complete_authentication_();
|
||||||
}
|
}
|
||||||
return resp;
|
return this->send_message(resp, ConnectResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|
||||||
|
bool APIConnection::send_ping_response(const PingRequest &msg) {
|
||||||
|
PingResponse resp;
|
||||||
|
return this->send_message(resp, PingResponse::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
|
||||||
DeviceInfoResponse resp{};
|
DeviceInfoResponse resp{};
|
||||||
#ifdef USE_API_PASSWORD
|
#ifdef USE_API_PASSWORD
|
||||||
resp.uses_password = true;
|
resp.uses_password = true;
|
||||||
#else
|
|
||||||
resp.uses_password = false;
|
|
||||||
#endif
|
#endif
|
||||||
resp.name = App.get_name();
|
resp.set_name(StringRef(App.get_name()));
|
||||||
resp.friendly_name = App.get_friendly_name();
|
resp.set_friendly_name(StringRef(App.get_friendly_name()));
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
resp.suggested_area = App.get_area();
|
resp.set_suggested_area(StringRef(App.get_area()));
|
||||||
#endif
|
#endif
|
||||||
resp.mac_address = get_mac_address_pretty();
|
// mac_address must store temporary string - will be valid during send_message call
|
||||||
resp.esphome_version = ESPHOME_VERSION;
|
std::string mac_address = get_mac_address_pretty();
|
||||||
resp.compilation_time = App.get_compilation_time();
|
resp.set_mac_address(StringRef(mac_address));
|
||||||
|
|
||||||
|
// Compile-time StringRef constants
|
||||||
|
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||||
|
resp.set_esphome_version(ESPHOME_VERSION_REF);
|
||||||
|
|
||||||
|
// get_compilation_time() returns temporary std::string - must store it
|
||||||
|
std::string compilation_time = App.get_compilation_time();
|
||||||
|
resp.set_compilation_time(StringRef(compilation_time));
|
||||||
|
|
||||||
|
// Compile-time StringRef constants for manufacturers
|
||||||
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
#if defined(USE_ESP8266) || defined(USE_ESP32)
|
||||||
resp.manufacturer = "Espressif";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Espressif");
|
||||||
#elif defined(USE_RP2040)
|
#elif defined(USE_RP2040)
|
||||||
resp.manufacturer = "Raspberry Pi";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Raspberry Pi");
|
||||||
#elif defined(USE_BK72XX)
|
#elif defined(USE_BK72XX)
|
||||||
resp.manufacturer = "Beken";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Beken");
|
||||||
#elif defined(USE_LN882X)
|
#elif defined(USE_LN882X)
|
||||||
resp.manufacturer = "Lightning";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Lightning");
|
||||||
#elif defined(USE_RTL87XX)
|
#elif defined(USE_RTL87XX)
|
||||||
resp.manufacturer = "Realtek";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Realtek");
|
||||||
#elif defined(USE_HOST)
|
#elif defined(USE_HOST)
|
||||||
resp.manufacturer = "Host";
|
static constexpr auto MANUFACTURER = StringRef::from_lit("Host");
|
||||||
#endif
|
#endif
|
||||||
resp.model = ESPHOME_BOARD;
|
resp.set_manufacturer(MANUFACTURER);
|
||||||
|
|
||||||
|
static constexpr auto MODEL = StringRef::from_lit(ESPHOME_BOARD);
|
||||||
|
resp.set_model(MODEL);
|
||||||
#ifdef USE_DEEP_SLEEP
|
#ifdef USE_DEEP_SLEEP
|
||||||
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
resp.has_deep_sleep = deep_sleep::global_has_deep_sleep;
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESPHOME_PROJECT_NAME
|
#ifdef ESPHOME_PROJECT_NAME
|
||||||
resp.project_name = ESPHOME_PROJECT_NAME;
|
static constexpr auto PROJECT_NAME = StringRef::from_lit(ESPHOME_PROJECT_NAME);
|
||||||
resp.project_version = ESPHOME_PROJECT_VERSION;
|
static constexpr auto PROJECT_VERSION = StringRef::from_lit(ESPHOME_PROJECT_VERSION);
|
||||||
|
resp.set_project_name(PROJECT_NAME);
|
||||||
|
resp.set_project_version(PROJECT_VERSION);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
resp.webserver_port = USE_WEBSERVER_PORT;
|
resp.webserver_port = USE_WEBSERVER_PORT;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
resp.legacy_bluetooth_proxy_version = bluetooth_proxy::global_bluetooth_proxy->get_legacy_version();
|
|
||||||
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
|
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
|
||||||
resp.bluetooth_mac_address = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
|
// bt_mac must store temporary string - will be valid during send_message call
|
||||||
|
std::string bluetooth_mac = bluetooth_proxy::global_bluetooth_proxy->get_bluetooth_mac_address_pretty();
|
||||||
|
resp.set_bluetooth_mac_address(StringRef(bluetooth_mac));
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
|
|
||||||
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
@ -1512,23 +1477,26 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
for (auto const &device : App.get_devices()) {
|
for (auto const &device : App.get_devices()) {
|
||||||
DeviceInfo device_info;
|
resp.devices.emplace_back();
|
||||||
|
auto &device_info = resp.devices.back();
|
||||||
device_info.device_id = device->get_device_id();
|
device_info.device_id = device->get_device_id();
|
||||||
device_info.name = device->get_name();
|
device_info.set_name(StringRef(device->get_name()));
|
||||||
device_info.area_id = device->get_area_id();
|
device_info.area_id = device->get_area_id();
|
||||||
resp.devices.push_back(device_info);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
for (auto const &area : App.get_areas()) {
|
for (auto const &area : App.get_areas()) {
|
||||||
AreaInfo area_info;
|
resp.areas.emplace_back();
|
||||||
|
auto &area_info = resp.areas.back();
|
||||||
area_info.area_id = area->get_area_id();
|
area_info.area_id = area->get_area_id();
|
||||||
area_info.name = area->get_name();
|
area_info.set_name(StringRef(area->get_name()));
|
||||||
resp.areas.push_back(area_info);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return resp;
|
|
||||||
|
return this->send_message(resp, DeviceInfoResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) {
|
||||||
for (auto &it : this->parent_->get_state_subs()) {
|
for (auto &it : this->parent_->get_state_subs()) {
|
||||||
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) {
|
||||||
@ -1536,6 +1504,7 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
@ -1550,28 +1519,27 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
|
bool APIConnection::send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
psk_t psk{};
|
|
||||||
NoiseEncryptionSetKeyResponse resp;
|
NoiseEncryptionSetKeyResponse resp;
|
||||||
|
resp.success = false;
|
||||||
|
|
||||||
|
psk_t psk{};
|
||||||
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
if (base64_decode(msg.key, psk.data(), msg.key.size()) != psk.size()) {
|
||||||
ESP_LOGW(TAG, "Invalid encryption key length");
|
ESP_LOGW(TAG, "Invalid encryption key length");
|
||||||
resp.success = false;
|
} else if (!this->parent_->save_noise_psk(psk, true)) {
|
||||||
return resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->parent_->save_noise_psk(psk, true)) {
|
|
||||||
ESP_LOGW(TAG, "Failed to save encryption key");
|
ESP_LOGW(TAG, "Failed to save encryption key");
|
||||||
resp.success = false;
|
} else {
|
||||||
return resp;
|
resp.success = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.success = true;
|
return this->send_message(resp, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE);
|
||||||
return resp;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
void APIConnection::subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||||
state_subs_at_ = 0;
|
state_subs_at_ = 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||||
if (this->flags_.remove)
|
if (this->flags_.remove)
|
||||||
return false;
|
return false;
|
||||||
@ -1609,10 +1577,12 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
|||||||
// Do not set last_traffic_ on send
|
// Do not set last_traffic_ on send
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
void APIConnection::on_unauthenticated_access() {
|
void APIConnection::on_unauthenticated_access() {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s access without authentication", this->get_client_combined_info().c_str());
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
void APIConnection::on_no_setup_connection() {
|
void APIConnection::on_no_setup_connection() {
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
|
ESP_LOGD(TAG, "%s access without full connection", this->get_client_combined_info().c_str());
|
||||||
@ -1671,6 +1641,10 @@ ProtoWriteBuffer APIConnection::allocate_batch_message_buffer(uint16_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::process_batch_() {
|
void APIConnection::process_batch_() {
|
||||||
|
// Ensure PacketInfo remains trivially destructible for our placement new approach
|
||||||
|
static_assert(std::is_trivially_destructible<PacketInfo>::value,
|
||||||
|
"PacketInfo must remain trivially destructible with this placement-new approach");
|
||||||
|
|
||||||
if (this->deferred_batch_.empty()) {
|
if (this->deferred_batch_.empty()) {
|
||||||
this->flags_.batch_scheduled = false;
|
this->flags_.batch_scheduled = false;
|
||||||
return;
|
return;
|
||||||
@ -1708,9 +1682,12 @@ void APIConnection::process_batch_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-allocate storage for packet info
|
size_t packets_to_process = std::min(num_items, MAX_PACKETS_PER_BATCH);
|
||||||
std::vector<PacketInfo> packet_info;
|
|
||||||
packet_info.reserve(num_items);
|
// Stack-allocated array for packet info
|
||||||
|
alignas(PacketInfo) char packet_info_storage[MAX_PACKETS_PER_BATCH * sizeof(PacketInfo)];
|
||||||
|
PacketInfo *packet_info = reinterpret_cast<PacketInfo *>(packet_info_storage);
|
||||||
|
size_t packet_count = 0;
|
||||||
|
|
||||||
// Cache these values to avoid repeated virtual calls
|
// Cache these values to avoid repeated virtual calls
|
||||||
const uint8_t header_padding = this->helper_->frame_header_padding();
|
const uint8_t header_padding = this->helper_->frame_header_padding();
|
||||||
@ -1742,8 +1719,8 @@ void APIConnection::process_batch_() {
|
|||||||
// The actual message data follows after the header padding
|
// The actual message data follows after the header padding
|
||||||
uint32_t current_offset = 0;
|
uint32_t current_offset = 0;
|
||||||
|
|
||||||
// Process items and encode directly to buffer
|
// Process items and encode directly to buffer (up to our limit)
|
||||||
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
for (size_t i = 0; i < packets_to_process; i++) {
|
||||||
const auto &item = this->deferred_batch_[i];
|
const auto &item = this->deferred_batch_[i];
|
||||||
// Try to encode message
|
// Try to encode message
|
||||||
// The creator will calculate overhead to determine if the message fits
|
// The creator will calculate overhead to determine if the message fits
|
||||||
@ -1757,7 +1734,11 @@ void APIConnection::process_batch_() {
|
|||||||
// Message was encoded successfully
|
// Message was encoded successfully
|
||||||
// payload_size is header_padding + actual payload size + footer_size
|
// payload_size is header_padding + actual payload size + footer_size
|
||||||
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
|
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
|
||||||
packet_info.emplace_back(item.message_type, current_offset, proto_payload_size);
|
// Use placement new to construct PacketInfo in pre-allocated stack array
|
||||||
|
// This avoids default-constructing all MAX_PACKETS_PER_BATCH elements
|
||||||
|
// Explicit destruction is not needed because PacketInfo is trivially destructible,
|
||||||
|
// as ensured by the static_assert in its definition.
|
||||||
|
new (&packet_info[packet_count++]) PacketInfo(item.message_type, current_offset, proto_payload_size);
|
||||||
|
|
||||||
// Update tracking variables
|
// Update tracking variables
|
||||||
items_processed++;
|
items_processed++;
|
||||||
@ -1783,8 +1764,8 @@ void APIConnection::process_batch_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send all collected packets
|
// Send all collected packets
|
||||||
APIError err =
|
APIError err = this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()},
|
||||||
this->helper_->write_protobuf_packets(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, packet_info);
|
std::span<const PacketInfo>(packet_info, packet_count));
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
on_fatal_error();
|
on_fatal_error();
|
||||||
ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
|
ESP_LOGW(TAG, "%s: Batch write failed %s errno=%d", this->get_client_combined_info().c_str(), api_error_to_str(err),
|
||||||
@ -1844,6 +1825,27 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
|
|||||||
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace api
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
} // namespace esphome
|
void APIConnection::process_state_subscriptions_() {
|
||||||
|
const auto &subs = this->parent_->get_state_subs();
|
||||||
|
if (this->state_subs_at_ >= static_cast<int>(subs.size())) {
|
||||||
|
this->state_subs_at_ = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &it = subs[this->state_subs_at_];
|
||||||
|
SubscribeHomeAssistantStateResponse resp;
|
||||||
|
resp.set_entity_id(StringRef(it.entity_id));
|
||||||
|
|
||||||
|
// Avoid string copy by directly using the optional's value if it exists
|
||||||
|
resp.set_attribute(it.attribute.has_value() ? StringRef(it.attribute.value()) : StringRef(""));
|
||||||
|
|
||||||
|
resp.once = it.once;
|
||||||
|
if (this->send_message(resp, SubscribeHomeAssistantStateResponse::MESSAGE_TYPE)) {
|
||||||
|
this->state_subs_at_++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_API_HOMEASSISTANT_STATES
|
||||||
|
|
||||||
|
} // namespace esphome::api
|
||||||
#endif
|
#endif
|
||||||
|
@ -13,13 +13,36 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
// Client information structure
|
||||||
|
struct ClientInfo {
|
||||||
|
std::string name; // Client name from Hello message
|
||||||
|
std::string peername; // IP:port from socket
|
||||||
|
|
||||||
|
std::string get_combined_info() const {
|
||||||
|
if (name == peername) {
|
||||||
|
// Before Hello message, both are the same
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return name + " (" + peername + ")";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Keepalive timeout in milliseconds
|
// Keepalive timeout in milliseconds
|
||||||
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
|
||||||
// Maximum number of entities to process in a single batch during initial state/info sending
|
// Maximum number of entities to process in a single batch during initial state/info sending
|
||||||
static constexpr size_t MAX_INITIAL_PER_BATCH = 20;
|
// This was increased from 20 to 24 after removing the unique_id field from entity info messages,
|
||||||
|
// which reduced message sizes allowing more entities per batch without exceeding packet limits
|
||||||
|
static constexpr size_t MAX_INITIAL_PER_BATCH = 24;
|
||||||
|
// Maximum number of packets to process in a single batch (platform-dependent)
|
||||||
|
// This limit exists to prevent stack overflow from the PacketInfo array in process_batch_
|
||||||
|
// Each PacketInfo is 8 bytes, so 64 * 8 = 512 bytes, 32 * 8 = 256 bytes
|
||||||
|
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||||
|
static constexpr size_t MAX_PACKETS_PER_BATCH = 64; // ESP32 has 8KB+ stack, HOST has plenty
|
||||||
|
#else
|
||||||
|
static constexpr size_t MAX_PACKETS_PER_BATCH = 32; // ESP8266/RP2040/etc have smaller stacks
|
||||||
|
#endif
|
||||||
|
|
||||||
class APIConnection : public APIServerConnection {
|
class APIConnection : public APIServerConnection {
|
||||||
public:
|
public:
|
||||||
@ -108,15 +131,16 @@ class APIConnection : public APIServerConnection {
|
|||||||
void media_player_command(const MediaPlayerCommandRequest &msg) override;
|
void media_player_command(const MediaPlayerCommandRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len);
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||||
if (!this->flags_.service_call_subscription)
|
if (!this->flags_.service_call_subscription)
|
||||||
return;
|
return;
|
||||||
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
|
this->send_message(call, HomeassistantServiceResponse::MESSAGE_TYPE);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
void unsubscribe_bluetooth_le_advertisements(const UnsubscribeBluetoothLEAdvertisementsRequest &msg) override;
|
||||||
bool send_bluetooth_le_advertisement(const BluetoothLEAdvertisementResponse &msg);
|
|
||||||
|
|
||||||
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
void bluetooth_device_request(const BluetoothDeviceRequest &msg) override;
|
||||||
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
|
void bluetooth_gatt_read(const BluetoothGATTReadRequest &msg) override;
|
||||||
@ -125,8 +149,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
void bluetooth_gatt_write_descriptor(const BluetoothGATTWriteDescriptorRequest &msg) override;
|
||||||
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
|
void bluetooth_gatt_get_services(const BluetoothGATTGetServicesRequest &msg) override;
|
||||||
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
|
void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) override;
|
||||||
BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
bool send_subscribe_bluetooth_connections_free_response(const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
||||||
const SubscribeBluetoothConnectionsFreeRequest &msg) override;
|
|
||||||
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
|
void bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequest &msg) override;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -144,8 +167,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
|
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
|
||||||
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
|
void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override;
|
||||||
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
|
void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override;
|
||||||
VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
|
bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) override;
|
||||||
const VoiceAssistantConfigurationRequest &msg) override;
|
|
||||||
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -168,15 +190,17 @@ class APIConnection : public APIServerConnection {
|
|||||||
// we initiated ping
|
// we initiated ping
|
||||||
this->flags_.sent_ping = false;
|
this->flags_.sent_ping = false;
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
void on_home_assistant_state_response(const HomeAssistantStateResponse &msg) override;
|
||||||
|
#endif
|
||||||
#ifdef USE_HOMEASSISTANT_TIME
|
#ifdef USE_HOMEASSISTANT_TIME
|
||||||
void on_get_time_response(const GetTimeResponse &value) override;
|
void on_get_time_response(const GetTimeResponse &value) override;
|
||||||
#endif
|
#endif
|
||||||
HelloResponse hello(const HelloRequest &msg) override;
|
bool send_hello_response(const HelloRequest &msg) override;
|
||||||
ConnectResponse connect(const ConnectRequest &msg) override;
|
bool send_connect_response(const ConnectRequest &msg) override;
|
||||||
DisconnectResponse disconnect(const DisconnectRequest &msg) override;
|
bool send_disconnect_response(const DisconnectRequest &msg) override;
|
||||||
PingResponse ping(const PingRequest &msg) override { return {}; }
|
bool send_ping_response(const PingRequest &msg) override;
|
||||||
DeviceInfoResponse device_info(const DeviceInfoRequest &msg) override;
|
bool send_device_info_response(const DeviceInfoRequest &msg) override;
|
||||||
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
void list_entities(const ListEntitiesRequest &msg) override { this->list_entities_iterator_.begin(); }
|
||||||
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
void subscribe_states(const SubscribeStatesRequest &msg) override {
|
||||||
this->flags_.state_subscription = true;
|
this->flags_.state_subscription = true;
|
||||||
@ -187,19 +211,20 @@ class APIConnection : public APIServerConnection {
|
|||||||
if (msg.dump_config)
|
if (msg.dump_config)
|
||||||
App.schedule_dump_config();
|
App.schedule_dump_config();
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) override {
|
||||||
this->flags_.service_call_subscription = true;
|
this->flags_.service_call_subscription = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
GetTimeResponse get_time(const GetTimeRequest &msg) override {
|
#endif
|
||||||
// TODO
|
bool send_get_time_response(const GetTimeRequest &msg) override;
|
||||||
return {};
|
|
||||||
}
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void execute_service(const ExecuteServiceRequest &msg) override;
|
void execute_service(const ExecuteServiceRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
|
bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) override;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool is_authenticated() override {
|
bool is_authenticated() override {
|
||||||
@ -211,7 +236,9 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
uint8_t get_log_subscription_level() const { return this->flags_.log_subscription; }
|
uint8_t get_log_subscription_level() const { return this->flags_.log_subscription; }
|
||||||
void on_fatal_error() override;
|
void on_fatal_error() override;
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
void on_unauthenticated_access() override;
|
void on_unauthenticated_access() override;
|
||||||
|
#endif
|
||||||
void on_no_setup_connection() override;
|
void on_no_setup_connection() override;
|
||||||
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
ProtoWriteBuffer create_buffer(uint32_t reserve_size) override {
|
||||||
// FIXME: ensure no recursive writes can happen
|
// FIXME: ensure no recursive writes can happen
|
||||||
@ -261,13 +288,7 @@ class APIConnection : public APIServerConnection {
|
|||||||
bool try_to_clear_buffer(bool log_out_of_space);
|
bool try_to_clear_buffer(bool log_out_of_space);
|
||||||
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
|
||||||
|
|
||||||
std::string get_client_combined_info() const {
|
std::string get_client_combined_info() const { return this->client_info_.get_combined_info(); }
|
||||||
if (this->client_info_ == this->client_peername_) {
|
|
||||||
// Before Hello message, both are the same (just IP:port)
|
|
||||||
return this->client_info_;
|
|
||||||
}
|
|
||||||
return this->client_info_ + " (" + this->client_peername_ + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer allocator methods for batch processing
|
// Buffer allocator methods for batch processing
|
||||||
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
|
ProtoWriteBuffer allocate_single_message_buffer(uint16_t size);
|
||||||
@ -277,6 +298,10 @@ class APIConnection : public APIServerConnection {
|
|||||||
// Helper function to handle authentication completion
|
// Helper function to handle authentication completion
|
||||||
void complete_authentication_();
|
void complete_authentication_();
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
void process_state_subscriptions_();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Non-template helper to encode any ProtoMessage
|
// Non-template helper to encode any ProtoMessage
|
||||||
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
|
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn,
|
||||||
uint32_t remaining_size, bool is_single);
|
uint32_t remaining_size, bool is_single);
|
||||||
@ -296,13 +321,18 @@ class APIConnection : public APIServerConnection {
|
|||||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||||
// Set common fields that are shared by all entity types
|
// Set common fields that are shared by all entity types
|
||||||
msg.key = entity->get_object_id_hash();
|
msg.key = entity->get_object_id_hash();
|
||||||
msg.object_id = entity->get_object_id();
|
// IMPORTANT: get_object_id() may return a temporary std::string
|
||||||
|
std::string object_id = entity->get_object_id();
|
||||||
|
msg.set_object_id(StringRef(object_id));
|
||||||
|
|
||||||
if (entity->has_own_name())
|
if (entity->has_own_name()) {
|
||||||
msg.name = entity->get_name();
|
msg.set_name(entity->get_name());
|
||||||
|
}
|
||||||
|
|
||||||
// Set common EntityBase properties
|
// Set common EntityBase properties
|
||||||
msg.icon = entity->get_icon();
|
#ifdef USE_ENTITY_ICON
|
||||||
|
msg.set_icon(entity->get_icon_ref());
|
||||||
|
#endif
|
||||||
msg.disabled_by_default = entity->is_disabled_by_default();
|
msg.disabled_by_default = entity->is_disabled_by_default();
|
||||||
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
msg.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||||
#ifdef USE_DEVICES
|
#ifdef USE_DEVICES
|
||||||
@ -471,13 +501,14 @@ class APIConnection : public APIServerConnection {
|
|||||||
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
std::unique_ptr<camera::CameraImageReader> image_reader_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned)
|
// Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each)
|
||||||
std::string client_info_;
|
ClientInfo client_info_;
|
||||||
std::string client_peername_;
|
|
||||||
|
|
||||||
// Group 4: 4-byte types
|
// Group 4: 4-byte types
|
||||||
uint32_t last_traffic_;
|
uint32_t last_traffic_;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
int state_subs_at_ = -1;
|
int state_subs_at_ = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Function pointer type for message encoding
|
// Function pointer type for message encoding
|
||||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||||
@ -707,6 +738,5 @@ class APIConnection : public APIServerConnection {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -8,16 +8,17 @@
|
|||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#ifdef USE_API_NOISE
|
|
||||||
#include "noise/protocol.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "api_noise_context.h"
|
|
||||||
#include "esphome/components/socket/socket.h"
|
#include "esphome/components/socket/socket.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
// uncomment to log raw packets
|
||||||
|
//#define HELPER_LOG_PACKETS
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
struct ClientInfo;
|
||||||
|
|
||||||
class ProtoWriteBuffer;
|
class ProtoWriteBuffer;
|
||||||
|
|
||||||
@ -40,7 +41,6 @@ struct PacketInfo {
|
|||||||
enum class APIError : uint16_t {
|
enum class APIError : uint16_t {
|
||||||
OK = 0,
|
OK = 0,
|
||||||
WOULD_BLOCK = 1001,
|
WOULD_BLOCK = 1001,
|
||||||
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
|
||||||
BAD_INDICATOR = 1003,
|
BAD_INDICATOR = 1003,
|
||||||
BAD_DATA_PACKET = 1004,
|
BAD_DATA_PACKET = 1004,
|
||||||
TCP_NODELAY_FAILED = 1005,
|
TCP_NODELAY_FAILED = 1005,
|
||||||
@ -51,16 +51,19 @@ enum class APIError : uint16_t {
|
|||||||
BAD_ARG = 1010,
|
BAD_ARG = 1010,
|
||||||
SOCKET_READ_FAILED = 1011,
|
SOCKET_READ_FAILED = 1011,
|
||||||
SOCKET_WRITE_FAILED = 1012,
|
SOCKET_WRITE_FAILED = 1012,
|
||||||
|
OUT_OF_MEMORY = 1018,
|
||||||
|
CONNECTION_CLOSED = 1022,
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
||||||
HANDSHAKESTATE_READ_FAILED = 1013,
|
HANDSHAKESTATE_READ_FAILED = 1013,
|
||||||
HANDSHAKESTATE_WRITE_FAILED = 1014,
|
HANDSHAKESTATE_WRITE_FAILED = 1014,
|
||||||
HANDSHAKESTATE_BAD_STATE = 1015,
|
HANDSHAKESTATE_BAD_STATE = 1015,
|
||||||
CIPHERSTATE_DECRYPT_FAILED = 1016,
|
CIPHERSTATE_DECRYPT_FAILED = 1016,
|
||||||
CIPHERSTATE_ENCRYPT_FAILED = 1017,
|
CIPHERSTATE_ENCRYPT_FAILED = 1017,
|
||||||
OUT_OF_MEMORY = 1018,
|
|
||||||
HANDSHAKESTATE_SETUP_FAILED = 1019,
|
HANDSHAKESTATE_SETUP_FAILED = 1019,
|
||||||
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
HANDSHAKESTATE_SPLIT_FAILED = 1020,
|
||||||
BAD_HANDSHAKE_ERROR_BYTE = 1021,
|
BAD_HANDSHAKE_ERROR_BYTE = 1021,
|
||||||
CONNECTION_CLOSED = 1022,
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
const char *api_error_to_str(APIError err);
|
const char *api_error_to_str(APIError err);
|
||||||
@ -68,7 +71,8 @@ const char *api_error_to_str(APIError err);
|
|||||||
class APIFrameHelper {
|
class APIFrameHelper {
|
||||||
public:
|
public:
|
||||||
APIFrameHelper() = default;
|
APIFrameHelper() = default;
|
||||||
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
|
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||||
|
: socket_owned_(std::move(socket)), client_info_(client_info) {
|
||||||
socket_ = socket_owned_.get();
|
socket_ = socket_owned_.get();
|
||||||
}
|
}
|
||||||
virtual ~APIFrameHelper() = default;
|
virtual ~APIFrameHelper() = default;
|
||||||
@ -94,8 +98,6 @@ class APIFrameHelper {
|
|||||||
}
|
}
|
||||||
return APIError::OK;
|
return APIError::OK;
|
||||||
}
|
}
|
||||||
// Give this helper a name for logging
|
|
||||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
|
||||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||||
// Write multiple protobuf packets in a single operation
|
// Write multiple protobuf packets in a single operation
|
||||||
// packets contains (message_type, offset, length) for each message in the buffer
|
// packets contains (message_type, offset, length) for each message in the buffer
|
||||||
@ -109,29 +111,28 @@ class APIFrameHelper {
|
|||||||
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Struct for holding parsed frame data
|
|
||||||
struct ParsedFrame {
|
|
||||||
std::vector<uint8_t> msg;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Buffer containing data to be sent
|
// Buffer containing data to be sent
|
||||||
struct SendBuffer {
|
struct SendBuffer {
|
||||||
std::vector<uint8_t> data;
|
std::unique_ptr<uint8_t[]> data;
|
||||||
uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
|
uint16_t size{0}; // Total size of the buffer
|
||||||
|
uint16_t offset{0}; // Current offset within the buffer
|
||||||
|
|
||||||
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
|
// Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
|
||||||
uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
|
uint16_t remaining() const { return size - offset; }
|
||||||
const uint8_t *current_data() const { return data.data() + offset; }
|
const uint8_t *current_data() const { return data.get() + offset; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common implementation for writing raw data to socket
|
// Common implementation for writing raw data to socket
|
||||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
||||||
|
|
||||||
// Try to send data from the tx buffer
|
// Try to send data from the tx buffer
|
||||||
APIError try_send_tx_buf_();
|
APIError try_send_tx_buf_();
|
||||||
|
|
||||||
// Helper method to buffer data from IOVs
|
// Helper method to buffer data from IOVs
|
||||||
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
|
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
|
||||||
|
|
||||||
|
// Common socket write error handling
|
||||||
|
APIError handle_socket_write_error_();
|
||||||
template<typename StateEnum>
|
template<typename StateEnum>
|
||||||
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
|
||||||
const std::string &info, StateEnum &state, StateEnum failed_state);
|
const std::string &info, StateEnum &state, StateEnum failed_state);
|
||||||
@ -161,10 +162,13 @@ class APIFrameHelper {
|
|||||||
|
|
||||||
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
||||||
std::deque<SendBuffer> tx_buf_;
|
std::deque<SendBuffer> tx_buf_;
|
||||||
std::string info_;
|
|
||||||
std::vector<struct iovec> reusable_iovs_;
|
std::vector<struct iovec> reusable_iovs_;
|
||||||
std::vector<uint8_t> rx_buf_;
|
std::vector<uint8_t> rx_buf_;
|
||||||
|
|
||||||
|
// Pointer to client info (4 bytes on 32-bit)
|
||||||
|
// Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
|
||||||
|
const ClientInfo *client_info_{nullptr};
|
||||||
|
|
||||||
// Group smaller types together
|
// Group smaller types together
|
||||||
uint16_t rx_buf_len_ = 0;
|
uint16_t rx_buf_len_ = 0;
|
||||||
State state_{State::INITIALIZE};
|
State state_{State::INITIALIZE};
|
||||||
@ -179,105 +183,6 @@ class APIFrameHelper {
|
|||||||
APIError handle_socket_read_result_(ssize_t received);
|
APIError handle_socket_read_result_(ssize_t received);
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
} // namespace esphome::api
|
||||||
class APINoiseFrameHelper : public APIFrameHelper {
|
|
||||||
public:
|
|
||||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
|
|
||||||
: APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
|
|
||||||
// Noise header structure:
|
|
||||||
// Pos 0: indicator (0x01)
|
|
||||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
|
||||||
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
|
||||||
// Pos 7+: actual payload data
|
|
||||||
frame_header_padding_ = 7;
|
|
||||||
}
|
|
||||||
~APINoiseFrameHelper() override;
|
|
||||||
APIError init() override;
|
|
||||||
APIError loop() override;
|
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
|
||||||
// Get the frame header padding required by this protocol
|
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
|
||||||
// Get the frame footer size required by this protocol
|
|
||||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
|
||||||
|
|
||||||
protected:
|
#endif // USE_API
|
||||||
APIError state_action_();
|
|
||||||
APIError try_read_frame_(ParsedFrame *frame);
|
|
||||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
|
||||||
APIError init_handshake_();
|
|
||||||
APIError check_handshake_finished_();
|
|
||||||
void send_explicit_handshake_reject_(const std::string &reason);
|
|
||||||
|
|
||||||
// Pointers first (4 bytes each)
|
|
||||||
NoiseHandshakeState *handshake_{nullptr};
|
|
||||||
NoiseCipherState *send_cipher_{nullptr};
|
|
||||||
NoiseCipherState *recv_cipher_{nullptr};
|
|
||||||
|
|
||||||
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
|
|
||||||
std::shared_ptr<APINoiseContext> ctx_;
|
|
||||||
|
|
||||||
// Vector (12 bytes on 32-bit)
|
|
||||||
std::vector<uint8_t> prologue_;
|
|
||||||
|
|
||||||
// NoiseProtocolId (size depends on implementation)
|
|
||||||
NoiseProtocolId nid_;
|
|
||||||
|
|
||||||
// Group small types together
|
|
||||||
// Fixed-size header buffer for noise protocol:
|
|
||||||
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
|
||||||
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
|
|
||||||
uint8_t rx_header_buf_[3];
|
|
||||||
uint8_t rx_header_buf_len_ = 0;
|
|
||||||
// 4 bytes total, no padding
|
|
||||||
};
|
|
||||||
#endif // USE_API_NOISE
|
|
||||||
|
|
||||||
#ifdef USE_API_PLAINTEXT
|
|
||||||
class APIPlaintextFrameHelper : public APIFrameHelper {
|
|
||||||
public:
|
|
||||||
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
|
|
||||||
// Plaintext header structure (worst case):
|
|
||||||
// Pos 0: indicator (0x00)
|
|
||||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
|
||||||
// Pos 4-5: message type varint (up to 2 bytes)
|
|
||||||
// Pos 6+: actual payload data
|
|
||||||
frame_header_padding_ = 6;
|
|
||||||
}
|
|
||||||
~APIPlaintextFrameHelper() override = default;
|
|
||||||
APIError init() override;
|
|
||||||
APIError loop() override;
|
|
||||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
|
||||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
|
||||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
|
||||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
|
||||||
// Get the frame footer size required by this protocol
|
|
||||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
APIError try_read_frame_(ParsedFrame *frame);
|
|
||||||
|
|
||||||
// Group 2-byte aligned types
|
|
||||||
uint16_t rx_header_parsed_type_ = 0;
|
|
||||||
uint16_t rx_header_parsed_len_ = 0;
|
|
||||||
|
|
||||||
// Group 1-byte types together
|
|
||||||
// Fixed-size header buffer for plaintext protocol:
|
|
||||||
// We now store the indicator byte + the two varints.
|
|
||||||
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
|
|
||||||
// 1 byte for indicator + 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
|
||||||
//
|
|
||||||
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
|
||||||
// attempting to process messages with headers that large would likely crash the
|
|
||||||
// ESP32 due to memory constraints.
|
|
||||||
uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
|
|
||||||
uint8_t rx_header_buf_pos_ = 0;
|
|
||||||
bool rx_header_parsed_ = false;
|
|
||||||
// 8 bytes total, no padding needed
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace api
|
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
|
||||||
|
583
esphome/components/api/api_frame_helper_noise.cpp
Normal file
583
esphome/components/api/api_frame_helper_noise.cpp
Normal file
@ -0,0 +1,583 @@
|
|||||||
|
#include "api_frame_helper_noise.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
#include "api_connection.h" // For ClientInfo struct
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "proto.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome::api {
|
||||||
|
|
||||||
|
static const char *const TAG = "api.noise";
|
||||||
|
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||||
|
static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||||
|
|
||||||
|
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#ifdef HELPER_LOG_PACKETS
|
||||||
|
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||||
|
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
|
||||||
|
#else
|
||||||
|
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||||
|
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Convert a noise error code to a readable error
|
||||||
|
std::string noise_err_to_str(int err) {
|
||||||
|
if (err == NOISE_ERROR_NO_MEMORY)
|
||||||
|
return "NO_MEMORY";
|
||||||
|
if (err == NOISE_ERROR_UNKNOWN_ID)
|
||||||
|
return "UNKNOWN_ID";
|
||||||
|
if (err == NOISE_ERROR_UNKNOWN_NAME)
|
||||||
|
return "UNKNOWN_NAME";
|
||||||
|
if (err == NOISE_ERROR_MAC_FAILURE)
|
||||||
|
return "MAC_FAILURE";
|
||||||
|
if (err == NOISE_ERROR_NOT_APPLICABLE)
|
||||||
|
return "NOT_APPLICABLE";
|
||||||
|
if (err == NOISE_ERROR_SYSTEM)
|
||||||
|
return "SYSTEM";
|
||||||
|
if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
|
||||||
|
return "REMOTE_KEY_REQUIRED";
|
||||||
|
if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
|
||||||
|
return "LOCAL_KEY_REQUIRED";
|
||||||
|
if (err == NOISE_ERROR_PSK_REQUIRED)
|
||||||
|
return "PSK_REQUIRED";
|
||||||
|
if (err == NOISE_ERROR_INVALID_LENGTH)
|
||||||
|
return "INVALID_LENGTH";
|
||||||
|
if (err == NOISE_ERROR_INVALID_PARAM)
|
||||||
|
return "INVALID_PARAM";
|
||||||
|
if (err == NOISE_ERROR_INVALID_STATE)
|
||||||
|
return "INVALID_STATE";
|
||||||
|
if (err == NOISE_ERROR_INVALID_NONCE)
|
||||||
|
return "INVALID_NONCE";
|
||||||
|
if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
|
||||||
|
return "INVALID_PRIVATE_KEY";
|
||||||
|
if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
|
||||||
|
return "INVALID_PUBLIC_KEY";
|
||||||
|
if (err == NOISE_ERROR_INVALID_FORMAT)
|
||||||
|
return "INVALID_FORMAT";
|
||||||
|
if (err == NOISE_ERROR_INVALID_SIGNATURE)
|
||||||
|
return "INVALID_SIGNATURE";
|
||||||
|
return to_string(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the frame helper, returns OK if successful.
|
||||||
|
APIError APINoiseFrameHelper::init() {
|
||||||
|
APIError err = init_common_();
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// init prologue
|
||||||
|
size_t old_size = prologue_.size();
|
||||||
|
prologue_.resize(old_size + PROLOGUE_INIT_LEN);
|
||||||
|
std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
|
||||||
|
|
||||||
|
state_ = State::CLIENT_HELLO;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
// Helper for handling handshake frame errors
|
||||||
|
APIError APINoiseFrameHelper::handle_handshake_frame_error_(APIError aerr) {
|
||||||
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
|
send_explicit_handshake_reject_("Bad indicator byte");
|
||||||
|
} else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
|
||||||
|
send_explicit_handshake_reject_("Bad handshake packet len");
|
||||||
|
}
|
||||||
|
return aerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for handling noise library errors
|
||||||
|
APIError APINoiseFrameHelper::handle_noise_error_(int err, const char *func_name, APIError api_err) {
|
||||||
|
if (err != 0) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("%s failed: %s", func_name, noise_err_to_str(err).c_str());
|
||||||
|
return api_err;
|
||||||
|
}
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run through handshake messages (if in that phase)
|
||||||
|
APIError APINoiseFrameHelper::loop() {
|
||||||
|
// During handshake phase, process as many actions as possible until we can't progress
|
||||||
|
// socket_->ready() stays true until next main loop, but state_action() will return
|
||||||
|
// WOULD_BLOCK when no more data is available to read
|
||||||
|
while (state_ != State::DATA && this->socket_->ready()) {
|
||||||
|
APIError err = state_action_();
|
||||||
|
if (err == APIError::WOULD_BLOCK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use base class implementation for buffer sending
|
||||||
|
return APIFrameHelper::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
|
*
|
||||||
|
* @param frame: The struct to hold the frame information in.
|
||||||
|
* msg_start: points to the start of the payload - this pointer is only valid until the next
|
||||||
|
* try_receive_raw_ call
|
||||||
|
*
|
||||||
|
* @return 0 if a full packet is in rx_buf_
|
||||||
|
* @return -1 if error, check errno.
|
||||||
|
*
|
||||||
|
* errno EWOULDBLOCK: Packet could not be read without blocking. Try again later.
|
||||||
|
* errno ENOMEM: Not enough memory for reading packet.
|
||||||
|
* errno API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
|
* errno API_ERROR_HANDSHAKE_PACKET_LEN: Packet too big for this phase.
|
||||||
|
*/
|
||||||
|
APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||||
|
if (frame == nullptr) {
|
||||||
|
HELPER_LOG("Bad argument for try_read_frame_");
|
||||||
|
return APIError::BAD_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read header
|
||||||
|
if (rx_header_buf_len_ < 3) {
|
||||||
|
// no header information yet
|
||||||
|
uint8_t to_read = 3 - rx_header_buf_len_;
|
||||||
|
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||||
|
APIError err = handle_socket_read_result_(received);
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
rx_header_buf_len_ += static_cast<uint8_t>(received);
|
||||||
|
if (static_cast<uint8_t>(received) != to_read) {
|
||||||
|
// not a full read
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_header_buf_[0] != 0x01) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||||
|
return APIError::BAD_INDICATOR;
|
||||||
|
}
|
||||||
|
// header reading done
|
||||||
|
}
|
||||||
|
|
||||||
|
// read body
|
||||||
|
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||||
|
|
||||||
|
if (state_ != State::DATA && msg_size > 128) {
|
||||||
|
// for handshake message only permit up to 128 bytes
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad packet len for handshake: %d", msg_size);
|
||||||
|
return APIError::BAD_HANDSHAKE_PACKET_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reserve space for body
|
||||||
|
if (rx_buf_.size() != msg_size) {
|
||||||
|
rx_buf_.resize(msg_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_buf_len_ < msg_size) {
|
||||||
|
// more data to read
|
||||||
|
uint16_t to_read = msg_size - rx_buf_len_;
|
||||||
|
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||||
|
APIError err = handle_socket_read_result_(received);
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||||
|
if (static_cast<uint16_t>(received) != to_read) {
|
||||||
|
// not all read
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_PACKET_RECEIVED(rx_buf_);
|
||||||
|
*frame = std::move(rx_buf_);
|
||||||
|
// consume msg
|
||||||
|
rx_buf_ = {};
|
||||||
|
rx_buf_len_ = 0;
|
||||||
|
rx_header_buf_len_ = 0;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To be called from read/write methods.
|
||||||
|
*
|
||||||
|
* This method runs through the internal handshake methods, if in that state.
|
||||||
|
*
|
||||||
|
* If the handshake is still active when this method returns and a read/write can't take place at
|
||||||
|
* the moment, returns WOULD_BLOCK.
|
||||||
|
* If an error occurred, returns that error. Only returns OK if the transport is ready for data
|
||||||
|
* traffic.
|
||||||
|
*/
|
||||||
|
APIError APINoiseFrameHelper::state_action_() {
|
||||||
|
int err;
|
||||||
|
APIError aerr;
|
||||||
|
if (state_ == State::INITIALIZE) {
|
||||||
|
HELPER_LOG("Bad state for method: %d", (int) state_);
|
||||||
|
return APIError::BAD_STATE;
|
||||||
|
}
|
||||||
|
if (state_ == State::CLIENT_HELLO) {
|
||||||
|
// waiting for client hello
|
||||||
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
|
if (aerr != APIError::OK) {
|
||||||
|
return handle_handshake_frame_error_(aerr);
|
||||||
|
}
|
||||||
|
// ignore contents, may be used in future for flags
|
||||||
|
// Resize for: existing prologue + 2 size bytes + frame data
|
||||||
|
size_t old_size = prologue_.size();
|
||||||
|
prologue_.resize(old_size + 2 + frame.size());
|
||||||
|
prologue_[old_size] = (uint8_t) (frame.size() >> 8);
|
||||||
|
prologue_[old_size + 1] = (uint8_t) frame.size();
|
||||||
|
std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
|
||||||
|
|
||||||
|
state_ = State::SERVER_HELLO;
|
||||||
|
}
|
||||||
|
if (state_ == State::SERVER_HELLO) {
|
||||||
|
// send server hello
|
||||||
|
const std::string &name = App.get_name();
|
||||||
|
const std::string &mac = get_mac_address();
|
||||||
|
|
||||||
|
std::vector<uint8_t> msg;
|
||||||
|
// Calculate positions and sizes
|
||||||
|
size_t name_len = name.size() + 1; // including null terminator
|
||||||
|
size_t mac_len = mac.size() + 1; // including null terminator
|
||||||
|
size_t name_offset = 1;
|
||||||
|
size_t mac_offset = name_offset + name_len;
|
||||||
|
size_t total_size = 1 + name_len + mac_len;
|
||||||
|
|
||||||
|
msg.resize(total_size);
|
||||||
|
|
||||||
|
// chosen proto
|
||||||
|
msg[0] = 0x01;
|
||||||
|
|
||||||
|
// node name, terminated by null byte
|
||||||
|
std::memcpy(msg.data() + name_offset, name.c_str(), name_len);
|
||||||
|
// node mac, terminated by null byte
|
||||||
|
std::memcpy(msg.data() + mac_offset, mac.c_str(), mac_len);
|
||||||
|
|
||||||
|
aerr = write_frame_(msg.data(), msg.size());
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
// start handshake
|
||||||
|
aerr = init_handshake_();
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
state_ = State::HANDSHAKE;
|
||||||
|
}
|
||||||
|
if (state_ == State::HANDSHAKE) {
|
||||||
|
int action = noise_handshakestate_get_action(handshake_);
|
||||||
|
if (action == NOISE_ACTION_READ_MESSAGE) {
|
||||||
|
// waiting for handshake msg
|
||||||
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
|
if (aerr != APIError::OK) {
|
||||||
|
return handle_handshake_frame_error_(aerr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame.empty()) {
|
||||||
|
send_explicit_handshake_reject_("Empty handshake message");
|
||||||
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
|
} else if (frame[0] != 0x00) {
|
||||||
|
HELPER_LOG("Bad handshake error byte: %u", frame[0]);
|
||||||
|
send_explicit_handshake_reject_("Bad handshake error byte");
|
||||||
|
return APIError::BAD_HANDSHAKE_ERROR_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoiseBuffer mbuf;
|
||||||
|
noise_buffer_init(mbuf);
|
||||||
|
noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
|
||||||
|
err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
|
||||||
|
if (err != 0) {
|
||||||
|
// Special handling for MAC failure
|
||||||
|
send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? "Handshake MAC failure" : "Handshake error");
|
||||||
|
return handle_noise_error_(err, "noise_handshakestate_read_message", APIError::HANDSHAKESTATE_READ_FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
aerr = check_handshake_finished_();
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
} else if (action == NOISE_ACTION_WRITE_MESSAGE) {
|
||||||
|
uint8_t buffer[65];
|
||||||
|
NoiseBuffer mbuf;
|
||||||
|
noise_buffer_init(mbuf);
|
||||||
|
noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
|
||||||
|
|
||||||
|
err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
|
||||||
|
APIError aerr_write =
|
||||||
|
handle_noise_error_(err, "noise_handshakestate_write_message", APIError::HANDSHAKESTATE_WRITE_FAILED);
|
||||||
|
if (aerr_write != APIError::OK)
|
||||||
|
return aerr_write;
|
||||||
|
buffer[0] = 0x00; // success
|
||||||
|
|
||||||
|
aerr = write_frame_(buffer, mbuf.size + 1);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
aerr = check_handshake_finished_();
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
} else {
|
||||||
|
// bad state for action
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad action for handshake: %d", action);
|
||||||
|
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state_ == State::CLOSED || state_ == State::FAILED) {
|
||||||
|
return APIError::BAD_STATE;
|
||||||
|
}
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
void APINoiseFrameHelper::send_explicit_handshake_reject_(const std::string &reason) {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.resize(reason.length() + 1);
|
||||||
|
data[0] = 0x01; // failure
|
||||||
|
|
||||||
|
// Copy error message in bulk
|
||||||
|
if (!reason.empty()) {
|
||||||
|
std::memcpy(data.data() + 1, reason.c_str(), reason.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporarily remove failed state
|
||||||
|
auto orig_state = state_;
|
||||||
|
state_ = State::EXPLICIT_REJECT;
|
||||||
|
write_frame_(data.data(), data.size());
|
||||||
|
state_ = orig_state;
|
||||||
|
}
|
||||||
|
APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
|
int err;
|
||||||
|
APIError aerr;
|
||||||
|
aerr = state_action_();
|
||||||
|
if (aerr != APIError::OK) {
|
||||||
|
return aerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ != State::DATA) {
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
NoiseBuffer mbuf;
|
||||||
|
noise_buffer_init(mbuf);
|
||||||
|
noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
|
||||||
|
err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
|
||||||
|
APIError decrypt_err = handle_noise_error_(err, "noise_cipherstate_decrypt", APIError::CIPHERSTATE_DECRYPT_FAILED);
|
||||||
|
if (decrypt_err != APIError::OK)
|
||||||
|
return decrypt_err;
|
||||||
|
|
||||||
|
uint16_t msg_size = mbuf.size;
|
||||||
|
uint8_t *msg_data = frame.data();
|
||||||
|
if (msg_size < 4) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad data packet: size %d too short", msg_size);
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||||
|
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||||
|
if (data_len > msg_size - 4) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->container = std::move(frame);
|
||||||
|
buffer->data_offset = 4;
|
||||||
|
buffer->data_len = data_len;
|
||||||
|
buffer->type = type;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
|
// Resize to include MAC space (required for Noise encryption)
|
||||||
|
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||||
|
PacketInfo packet{type, 0,
|
||||||
|
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||||
|
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||||
|
APIError aerr = state_action_();
|
||||||
|
if (aerr != APIError::OK) {
|
||||||
|
return aerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ != State::DATA) {
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packets.empty()) {
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
|
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||||
|
|
||||||
|
this->reusable_iovs_.clear();
|
||||||
|
this->reusable_iovs_.reserve(packets.size());
|
||||||
|
uint16_t total_write_len = 0;
|
||||||
|
|
||||||
|
// We need to encrypt each packet in place
|
||||||
|
for (const auto &packet : packets) {
|
||||||
|
// The buffer already has padding at offset
|
||||||
|
uint8_t *buf_start = buffer_data + packet.offset;
|
||||||
|
|
||||||
|
// Write noise header
|
||||||
|
buf_start[0] = 0x01; // indicator
|
||||||
|
// buf_start[1], buf_start[2] to be set after encryption
|
||||||
|
|
||||||
|
// Write message header (to be encrypted)
|
||||||
|
const uint8_t msg_offset = 3;
|
||||||
|
buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
|
||||||
|
buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
|
||||||
|
buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
|
||||||
|
buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
|
||||||
|
// payload data is already in the buffer starting at offset + 7
|
||||||
|
|
||||||
|
// Make sure we have space for MAC
|
||||||
|
// The buffer should already have been sized appropriately
|
||||||
|
|
||||||
|
// Encrypt the message in place
|
||||||
|
NoiseBuffer mbuf;
|
||||||
|
noise_buffer_init(mbuf);
|
||||||
|
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
|
||||||
|
4 + packet.payload_size + frame_footer_size_);
|
||||||
|
|
||||||
|
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||||
|
APIError aerr = handle_noise_error_(err, "noise_cipherstate_encrypt", APIError::CIPHERSTATE_ENCRYPT_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
// Fill in the encrypted size
|
||||||
|
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
|
||||||
|
buf_start[2] = static_cast<uint8_t>(mbuf.size);
|
||||||
|
|
||||||
|
// Add iovec for this encrypted packet
|
||||||
|
size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||||
|
this->reusable_iovs_.push_back({buf_start, packet_len});
|
||||||
|
total_write_len += packet_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all encrypted packets in one writev call
|
||||||
|
return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||||
|
uint8_t header[3];
|
||||||
|
header[0] = 0x01; // indicator
|
||||||
|
header[1] = (uint8_t) (len >> 8);
|
||||||
|
header[2] = (uint8_t) len;
|
||||||
|
|
||||||
|
struct iovec iov[2];
|
||||||
|
iov[0].iov_base = header;
|
||||||
|
iov[0].iov_len = 3;
|
||||||
|
if (len == 0) {
|
||||||
|
return this->write_raw_(iov, 1, 3); // Just header
|
||||||
|
}
|
||||||
|
iov[1].iov_base = const_cast<uint8_t *>(data);
|
||||||
|
iov[1].iov_len = len;
|
||||||
|
|
||||||
|
return this->write_raw_(iov, 2, 3 + len); // Header + data
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initiate the data structures for the handshake.
|
||||||
|
*
|
||||||
|
* @return 0 on success, -1 on error (check errno)
|
||||||
|
*/
|
||||||
|
APIError APINoiseFrameHelper::init_handshake_() {
|
||||||
|
int err;
|
||||||
|
memset(&nid_, 0, sizeof(nid_));
|
||||||
|
// const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
|
||||||
|
// err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
|
||||||
|
nid_.pattern_id = NOISE_PATTERN_NN;
|
||||||
|
nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
|
||||||
|
nid_.dh_id = NOISE_DH_CURVE25519;
|
||||||
|
nid_.prefix_id = NOISE_PREFIX_STANDARD;
|
||||||
|
nid_.hybrid_id = NOISE_DH_NONE;
|
||||||
|
nid_.hash_id = NOISE_HASH_SHA256;
|
||||||
|
nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
|
||||||
|
|
||||||
|
err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
|
||||||
|
APIError aerr = handle_noise_error_(err, "noise_handshakestate_new_by_id", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
const auto &psk = ctx_->get_psk();
|
||||||
|
err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
|
||||||
|
aerr = handle_noise_error_(err, "noise_handshakestate_set_pre_shared_key", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
|
||||||
|
aerr = handle_noise_error_(err, "noise_handshakestate_set_prologue", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
// set_prologue copies it into handshakestate, so we can get rid of it now
|
||||||
|
prologue_ = {};
|
||||||
|
|
||||||
|
err = noise_handshakestate_start(handshake_);
|
||||||
|
aerr = handle_noise_error_(err, "noise_handshakestate_start", APIError::HANDSHAKESTATE_SETUP_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
APIError APINoiseFrameHelper::check_handshake_finished_() {
|
||||||
|
assert(state_ == State::HANDSHAKE);
|
||||||
|
|
||||||
|
int action = noise_handshakestate_get_action(handshake_);
|
||||||
|
if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
|
||||||
|
return APIError::OK;
|
||||||
|
if (action != NOISE_ACTION_SPLIT) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad action for handshake: %d", action);
|
||||||
|
return APIError::HANDSHAKESTATE_BAD_STATE;
|
||||||
|
}
|
||||||
|
int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
|
||||||
|
APIError aerr = handle_noise_error_(err, "noise_handshakestate_split", APIError::HANDSHAKESTATE_SPLIT_FAILED);
|
||||||
|
if (aerr != APIError::OK)
|
||||||
|
return aerr;
|
||||||
|
|
||||||
|
frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
|
||||||
|
|
||||||
|
HELPER_LOG("Handshake complete!");
|
||||||
|
noise_handshakestate_free(handshake_);
|
||||||
|
handshake_ = nullptr;
|
||||||
|
state_ = State::DATA;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
APINoiseFrameHelper::~APINoiseFrameHelper() {
|
||||||
|
if (handshake_ != nullptr) {
|
||||||
|
noise_handshakestate_free(handshake_);
|
||||||
|
handshake_ = nullptr;
|
||||||
|
}
|
||||||
|
if (send_cipher_ != nullptr) {
|
||||||
|
noise_cipherstate_free(send_cipher_);
|
||||||
|
send_cipher_ = nullptr;
|
||||||
|
}
|
||||||
|
if (recv_cipher_ != nullptr) {
|
||||||
|
noise_cipherstate_free(recv_cipher_);
|
||||||
|
recv_cipher_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
|
||||||
|
void noise_rand_bytes(void *output, size_t len) {
|
||||||
|
if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
|
||||||
|
ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
|
||||||
|
arch_restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::api
|
||||||
|
#endif // USE_API_NOISE
|
||||||
|
#endif // USE_API
|
68
esphome/components/api/api_frame_helper_noise.h
Normal file
68
esphome/components/api/api_frame_helper_noise.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "api_frame_helper.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
|
#include "noise/protocol.h"
|
||||||
|
#include "api_noise_context.h"
|
||||||
|
|
||||||
|
namespace esphome::api {
|
||||||
|
|
||||||
|
class APINoiseFrameHelper : public APIFrameHelper {
|
||||||
|
public:
|
||||||
|
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx,
|
||||||
|
const ClientInfo *client_info)
|
||||||
|
: APIFrameHelper(std::move(socket), client_info), ctx_(std::move(ctx)) {
|
||||||
|
// Noise header structure:
|
||||||
|
// Pos 0: indicator (0x01)
|
||||||
|
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||||
|
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||||
|
// Pos 7+: actual payload data
|
||||||
|
frame_header_padding_ = 7;
|
||||||
|
}
|
||||||
|
~APINoiseFrameHelper() override;
|
||||||
|
APIError init() override;
|
||||||
|
APIError loop() override;
|
||||||
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
// Get the frame header padding required by this protocol
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
APIError state_action_();
|
||||||
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
|
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||||
|
APIError init_handshake_();
|
||||||
|
APIError check_handshake_finished_();
|
||||||
|
void send_explicit_handshake_reject_(const std::string &reason);
|
||||||
|
APIError handle_handshake_frame_error_(APIError aerr);
|
||||||
|
APIError handle_noise_error_(int err, const char *func_name, APIError api_err);
|
||||||
|
|
||||||
|
// Pointers first (4 bytes each)
|
||||||
|
NoiseHandshakeState *handshake_{nullptr};
|
||||||
|
NoiseCipherState *send_cipher_{nullptr};
|
||||||
|
NoiseCipherState *recv_cipher_{nullptr};
|
||||||
|
|
||||||
|
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
|
||||||
|
std::shared_ptr<APINoiseContext> ctx_;
|
||||||
|
|
||||||
|
// Vector (12 bytes on 32-bit)
|
||||||
|
std::vector<uint8_t> prologue_;
|
||||||
|
|
||||||
|
// NoiseProtocolId (size depends on implementation)
|
||||||
|
NoiseProtocolId nid_;
|
||||||
|
|
||||||
|
// Group small types together
|
||||||
|
// Fixed-size header buffer for noise protocol:
|
||||||
|
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||||
|
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
|
||||||
|
uint8_t rx_header_buf_[3];
|
||||||
|
uint8_t rx_header_buf_len_ = 0;
|
||||||
|
// 4 bytes total, no padding
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::api
|
||||||
|
#endif // USE_API_NOISE
|
||||||
|
#endif // USE_API
|
290
esphome/components/api/api_frame_helper_plaintext.cpp
Normal file
290
esphome/components/api/api_frame_helper_plaintext.cpp
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
#include "api_frame_helper_plaintext.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_PLAINTEXT
|
||||||
|
#include "api_connection.h" // For ClientInfo struct
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "proto.h"
|
||||||
|
#include <cstring>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
namespace esphome::api {
|
||||||
|
|
||||||
|
static const char *const TAG = "api.plaintext";
|
||||||
|
|
||||||
|
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#ifdef HELPER_LOG_PACKETS
|
||||||
|
#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
|
||||||
|
#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
|
||||||
|
#else
|
||||||
|
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||||
|
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Initialize the frame helper, returns OK if successful.
|
||||||
|
APIError APIPlaintextFrameHelper::init() {
|
||||||
|
APIError err = init_common_();
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = State::DATA;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
APIError APIPlaintextFrameHelper::loop() {
|
||||||
|
if (state_ != State::DATA) {
|
||||||
|
return APIError::BAD_STATE;
|
||||||
|
}
|
||||||
|
// Use base class implementation for buffer sending
|
||||||
|
return APIFrameHelper::loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||||
|
*
|
||||||
|
* @param frame: The struct to hold the frame information in.
|
||||||
|
* msg: store the parsed frame in that struct
|
||||||
|
*
|
||||||
|
* @return See APIError
|
||||||
|
*
|
||||||
|
* error API_ERROR_BAD_INDICATOR: Bad indicator byte at start of frame.
|
||||||
|
*/
|
||||||
|
APIError APIPlaintextFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
|
||||||
|
if (frame == nullptr) {
|
||||||
|
HELPER_LOG("Bad argument for try_read_frame_");
|
||||||
|
return APIError::BAD_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read header
|
||||||
|
while (!rx_header_parsed_) {
|
||||||
|
// Now that we know when the socket is ready, we can read up to 3 bytes
|
||||||
|
// into the rx_header_buf_ before we have to switch back to reading
|
||||||
|
// one byte at a time to ensure we don't read past the message and
|
||||||
|
// into the next one.
|
||||||
|
|
||||||
|
// Read directly into rx_header_buf_ at the current position
|
||||||
|
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
|
||||||
|
ssize_t received =
|
||||||
|
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
|
||||||
|
APIError err = handle_socket_read_result_(received);
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this was the first read, validate the indicator byte
|
||||||
|
if (rx_header_buf_pos_ == 0 && received > 0) {
|
||||||
|
if (rx_header_buf_[0] != 0x00) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||||
|
return APIError::BAD_INDICATOR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rx_header_buf_pos_ += received;
|
||||||
|
|
||||||
|
// Check for buffer overflow
|
||||||
|
if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Header buffer overflow");
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse
|
||||||
|
if (rx_header_buf_pos_ < 3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we have at least 3 bytes total:
|
||||||
|
// - Validated indicator byte (0x00) stored at position 0
|
||||||
|
// - At least 2 bytes in the buffer for the varints
|
||||||
|
// Buffer layout:
|
||||||
|
// [0]: indicator byte (0x00)
|
||||||
|
// [1-3]: Message size varint (variable length)
|
||||||
|
// - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
|
||||||
|
// - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
|
||||||
|
// [2-5]: Message type varint (variable length)
|
||||||
|
// We now attempt to parse both varints. If either is incomplete,
|
||||||
|
// we'll continue reading more bytes.
|
||||||
|
|
||||||
|
// Skip indicator byte at position 0
|
||||||
|
uint8_t varint_pos = 1;
|
||||||
|
uint32_t consumed = 0;
|
||||||
|
|
||||||
|
auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
|
||||||
|
if (!msg_size_varint.has_value()) {
|
||||||
|
// not enough data there yet
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
|
||||||
|
std::numeric_limits<uint16_t>::max());
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
rx_header_parsed_len_ = msg_size_varint->as_uint16();
|
||||||
|
|
||||||
|
// Move to next varint position
|
||||||
|
varint_pos += consumed;
|
||||||
|
|
||||||
|
auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
|
||||||
|
if (!msg_type_varint.has_value()) {
|
||||||
|
// not enough data there yet
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
|
||||||
|
state_ = State::FAILED;
|
||||||
|
HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
|
||||||
|
std::numeric_limits<uint16_t>::max());
|
||||||
|
return APIError::BAD_DATA_PACKET;
|
||||||
|
}
|
||||||
|
rx_header_parsed_type_ = msg_type_varint->as_uint16();
|
||||||
|
rx_header_parsed_ = true;
|
||||||
|
}
|
||||||
|
// header reading done
|
||||||
|
|
||||||
|
// reserve space for body
|
||||||
|
if (rx_buf_.size() != rx_header_parsed_len_) {
|
||||||
|
rx_buf_.resize(rx_header_parsed_len_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_buf_len_ < rx_header_parsed_len_) {
|
||||||
|
// more data to read
|
||||||
|
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||||
|
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||||
|
APIError err = handle_socket_read_result_(received);
|
||||||
|
if (err != APIError::OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||||
|
if (static_cast<uint16_t>(received) != to_read) {
|
||||||
|
// not all read
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_PACKET_RECEIVED(rx_buf_);
|
||||||
|
*frame = std::move(rx_buf_);
|
||||||
|
// consume msg
|
||||||
|
rx_buf_ = {};
|
||||||
|
rx_buf_len_ = 0;
|
||||||
|
rx_header_buf_pos_ = 0;
|
||||||
|
rx_header_parsed_ = false;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||||
|
APIError aerr;
|
||||||
|
|
||||||
|
if (state_ != State::DATA) {
|
||||||
|
return APIError::WOULD_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> frame;
|
||||||
|
aerr = try_read_frame_(&frame);
|
||||||
|
if (aerr != APIError::OK) {
|
||||||
|
if (aerr == APIError::BAD_INDICATOR) {
|
||||||
|
// Make sure to tell the remote that we don't
|
||||||
|
// understand the indicator byte so it knows
|
||||||
|
// we do not support it.
|
||||||
|
struct iovec iov[1];
|
||||||
|
// The \x00 first byte is the marker for plaintext.
|
||||||
|
//
|
||||||
|
// The remote will know how to handle the indicator byte,
|
||||||
|
// but it likely won't understand the rest of the message.
|
||||||
|
//
|
||||||
|
// We must send at least 3 bytes to be read, so we add
|
||||||
|
// a message after the indicator byte to ensures its long
|
||||||
|
// enough and can aid in debugging.
|
||||||
|
const char msg[] = "\x00"
|
||||||
|
"Bad indicator byte";
|
||||||
|
iov[0].iov_base = (void *) msg;
|
||||||
|
iov[0].iov_len = 19;
|
||||||
|
this->write_raw_(iov, 1, 19);
|
||||||
|
}
|
||||||
|
return aerr;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer->container = std::move(frame);
|
||||||
|
buffer->data_offset = 0;
|
||||||
|
buffer->data_len = rx_header_parsed_len_;
|
||||||
|
buffer->type = rx_header_parsed_type_;
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||||
|
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||||
|
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||||
|
if (state_ != State::DATA) {
|
||||||
|
return APIError::BAD_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packets.empty()) {
|
||||||
|
return APIError::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||||
|
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||||
|
|
||||||
|
this->reusable_iovs_.clear();
|
||||||
|
this->reusable_iovs_.reserve(packets.size());
|
||||||
|
uint16_t total_write_len = 0;
|
||||||
|
|
||||||
|
for (const auto &packet : packets) {
|
||||||
|
// Calculate varint sizes for header layout
|
||||||
|
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
||||||
|
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
||||||
|
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||||
|
|
||||||
|
// Calculate where to start writing the header
|
||||||
|
// The header starts at the latest possible position to minimize unused padding
|
||||||
|
//
|
||||||
|
// Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
|
||||||
|
// [0-2] - Unused padding
|
||||||
|
// [3] - 0x00 indicator byte
|
||||||
|
// [4] - Payload size varint (1 byte, for sizes 0-127)
|
||||||
|
// [5] - Message type varint (1 byte, for types 0-127)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
//
|
||||||
|
// Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
|
||||||
|
// [0-1] - Unused padding
|
||||||
|
// [2] - 0x00 indicator byte
|
||||||
|
// [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
|
||||||
|
// [5] - Message type varint (1 byte, for types 0-127)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
//
|
||||||
|
// Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
|
||||||
|
// [0] - 0x00 indicator byte
|
||||||
|
// [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
|
||||||
|
// [4-5] - Message type varint (2 bytes, for types 128-32767)
|
||||||
|
// [6...] - Actual payload data
|
||||||
|
//
|
||||||
|
// The message starts at offset + frame_header_padding_
|
||||||
|
// So we write the header starting at offset + frame_header_padding_ - total_header_len
|
||||||
|
uint8_t *buf_start = buffer_data + packet.offset;
|
||||||
|
uint32_t header_offset = frame_header_padding_ - total_header_len;
|
||||||
|
|
||||||
|
// Write the plaintext header
|
||||||
|
buf_start[header_offset] = 0x00; // indicator
|
||||||
|
|
||||||
|
// Encode varints directly into buffer
|
||||||
|
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||||
|
ProtoVarInt(packet.message_type)
|
||||||
|
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||||
|
|
||||||
|
// Add iovec for this packet (header + payload)
|
||||||
|
size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
|
||||||
|
this->reusable_iovs_.push_back({buf_start + header_offset, packet_len});
|
||||||
|
total_write_len += packet_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send all packets in one writev call
|
||||||
|
return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::api
|
||||||
|
#endif // USE_API_PLAINTEXT
|
||||||
|
#endif // USE_API
|
53
esphome/components/api/api_frame_helper_plaintext.h
Normal file
53
esphome/components/api/api_frame_helper_plaintext.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "api_frame_helper.h"
|
||||||
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_PLAINTEXT
|
||||||
|
|
||||||
|
namespace esphome::api {
|
||||||
|
|
||||||
|
class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||||
|
public:
|
||||||
|
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
|
||||||
|
: APIFrameHelper(std::move(socket), client_info) {
|
||||||
|
// Plaintext header structure (worst case):
|
||||||
|
// Pos 0: indicator (0x00)
|
||||||
|
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||||
|
// Pos 4-5: message type varint (up to 2 bytes)
|
||||||
|
// Pos 6+: actual payload data
|
||||||
|
frame_header_padding_ = 6;
|
||||||
|
}
|
||||||
|
~APIPlaintextFrameHelper() override = default;
|
||||||
|
APIError init() override;
|
||||||
|
APIError loop() override;
|
||||||
|
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||||
|
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||||
|
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||||
|
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||||
|
// Get the frame footer size required by this protocol
|
||||||
|
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
APIError try_read_frame_(std::vector<uint8_t> *frame);
|
||||||
|
|
||||||
|
// Group 2-byte aligned types
|
||||||
|
uint16_t rx_header_parsed_type_ = 0;
|
||||||
|
uint16_t rx_header_parsed_len_ = 0;
|
||||||
|
|
||||||
|
// Group 1-byte types together
|
||||||
|
// Fixed-size header buffer for plaintext protocol:
|
||||||
|
// We now store the indicator byte + the two varints.
|
||||||
|
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
|
||||||
|
// 1 byte for indicator + 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
|
||||||
|
//
|
||||||
|
// While varints could theoretically be up to 10 bytes each for 64-bit values,
|
||||||
|
// attempting to process messages with headers that large would likely crash the
|
||||||
|
// ESP32 due to memory constraints.
|
||||||
|
uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
|
||||||
|
uint8_t rx_header_buf_pos_ = 0;
|
||||||
|
bool rx_header_parsed_ = false;
|
||||||
|
// 8 bytes total, no padding needed
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace esphome::api
|
||||||
|
#endif // USE_API_PLAINTEXT
|
||||||
|
#endif // USE_API
|
@ -3,8 +3,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
using psk_t = std::array<uint8_t, 32>;
|
using psk_t = std::array<uint8_t, 32>;
|
||||||
@ -28,5 +27,4 @@ class APINoiseContext {
|
|||||||
};
|
};
|
||||||
#endif // USE_API_NOISE
|
#endif // USE_API_NOISE
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -26,4 +26,6 @@ extend google.protobuf.MessageOptions {
|
|||||||
|
|
||||||
extend google.protobuf.FieldOptions {
|
extend google.protobuf.FieldOptions {
|
||||||
optional string field_ifdef = 1042;
|
optional string field_ifdef = 1042;
|
||||||
|
optional uint32 fixed_array_size = 50007;
|
||||||
|
optional bool no_zero_copy = 50008 [default=false];
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/string_ref.h"
|
||||||
|
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
namespace enums {
|
namespace enums {
|
||||||
|
|
||||||
@ -17,27 +17,13 @@ enum EntityCategory : uint32_t {
|
|||||||
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
||||||
};
|
};
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
enum LegacyCoverState : uint32_t {
|
|
||||||
LEGACY_COVER_STATE_OPEN = 0,
|
|
||||||
LEGACY_COVER_STATE_CLOSED = 1,
|
|
||||||
};
|
|
||||||
enum CoverOperation : uint32_t {
|
enum CoverOperation : uint32_t {
|
||||||
COVER_OPERATION_IDLE = 0,
|
COVER_OPERATION_IDLE = 0,
|
||||||
COVER_OPERATION_IS_OPENING = 1,
|
COVER_OPERATION_IS_OPENING = 1,
|
||||||
COVER_OPERATION_IS_CLOSING = 2,
|
COVER_OPERATION_IS_CLOSING = 2,
|
||||||
};
|
};
|
||||||
enum LegacyCoverCommand : uint32_t {
|
|
||||||
LEGACY_COVER_COMMAND_OPEN = 0,
|
|
||||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
|
||||||
LEGACY_COVER_COMMAND_STOP = 2,
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
enum FanSpeed : uint32_t {
|
|
||||||
FAN_SPEED_LOW = 0,
|
|
||||||
FAN_SPEED_MEDIUM = 1,
|
|
||||||
FAN_SPEED_HIGH = 2,
|
|
||||||
};
|
|
||||||
enum FanDirection : uint32_t {
|
enum FanDirection : uint32_t {
|
||||||
FAN_DIRECTION_FORWARD = 0,
|
FAN_DIRECTION_FORWARD = 0,
|
||||||
FAN_DIRECTION_REVERSE = 1,
|
FAN_DIRECTION_REVERSE = 1,
|
||||||
@ -65,11 +51,6 @@ enum SensorStateClass : uint32_t {
|
|||||||
STATE_CLASS_TOTAL_INCREASING = 2,
|
STATE_CLASS_TOTAL_INCREASING = 2,
|
||||||
STATE_CLASS_TOTAL = 3,
|
STATE_CLASS_TOTAL = 3,
|
||||||
};
|
};
|
||||||
enum SensorLastResetType : uint32_t {
|
|
||||||
LAST_RESET_NONE = 0,
|
|
||||||
LAST_RESET_NEVER = 1,
|
|
||||||
LAST_RESET_AUTO = 2,
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
@ -288,13 +269,20 @@ enum UpdateCommand : uint32_t {
|
|||||||
class InfoResponseProtoMessage : public ProtoMessage {
|
class InfoResponseProtoMessage : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
~InfoResponseProtoMessage() override = default;
|
~InfoResponseProtoMessage() override = default;
|
||||||
std::string object_id{};
|
StringRef object_id_ref_{};
|
||||||
|
void set_object_id(const StringRef &ref) { this->object_id_ref_ = ref; }
|
||||||
uint32_t key{0};
|
uint32_t key{0};
|
||||||
std::string name{};
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
bool disabled_by_default{false};
|
bool disabled_by_default{false};
|
||||||
std::string icon{};
|
#ifdef USE_ENTITY_ICON
|
||||||
|
StringRef icon_ref_{};
|
||||||
|
void set_icon(const StringRef &ref) { this->icon_ref_ = ref; }
|
||||||
|
#endif
|
||||||
enums::EntityCategory entity_category{};
|
enums::EntityCategory entity_category{};
|
||||||
|
#ifdef USE_DEVICES
|
||||||
uint32_t device_id{0};
|
uint32_t device_id{0};
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
@ -303,7 +291,9 @@ class StateResponseProtoMessage : public ProtoMessage {
|
|||||||
public:
|
public:
|
||||||
~StateResponseProtoMessage() override = default;
|
~StateResponseProtoMessage() override = default;
|
||||||
uint32_t key{0};
|
uint32_t key{0};
|
||||||
|
#ifdef USE_DEVICES
|
||||||
uint32_t device_id{0};
|
uint32_t device_id{0};
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
@ -312,7 +302,9 @@ class CommandProtoMessage : public ProtoDecodableMessage {
|
|||||||
public:
|
public:
|
||||||
~CommandProtoMessage() override = default;
|
~CommandProtoMessage() override = default;
|
||||||
uint32_t key{0};
|
uint32_t key{0};
|
||||||
|
#ifdef USE_DEVICES
|
||||||
uint32_t device_id{0};
|
uint32_t device_id{0};
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
@ -343,8 +335,10 @@ class HelloResponse : public ProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
uint32_t api_version_major{0};
|
uint32_t api_version_major{0};
|
||||||
uint32_t api_version_minor{0};
|
uint32_t api_version_minor{0};
|
||||||
std::string server_info{};
|
StringRef server_info_ref_{};
|
||||||
std::string name{};
|
void set_server_info(const StringRef &ref) { this->server_info_ref_ = ref; }
|
||||||
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -449,10 +443,12 @@ class DeviceInfoRequest : public ProtoDecodableMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
#ifdef USE_AREAS
|
||||||
class AreaInfo : public ProtoMessage {
|
class AreaInfo : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t area_id{0};
|
uint32_t area_id{0};
|
||||||
std::string name{};
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -461,10 +457,13 @@ class AreaInfo : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DEVICES
|
||||||
class DeviceInfo : public ProtoMessage {
|
class DeviceInfo : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint32_t device_id{0};
|
uint32_t device_id{0};
|
||||||
std::string name{};
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
uint32_t area_id{0};
|
uint32_t area_id{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -474,50 +473,58 @@ class DeviceInfo : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
class DeviceInfoResponse : public ProtoMessage {
|
class DeviceInfoResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 10;
|
static constexpr uint8_t MESSAGE_TYPE = 10;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 219;
|
static constexpr uint8_t ESTIMATED_SIZE = 211;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "device_info_response"; }
|
const char *message_name() const override { return "device_info_response"; }
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
bool uses_password{false};
|
bool uses_password{false};
|
||||||
std::string name{};
|
#endif
|
||||||
std::string mac_address{};
|
StringRef name_ref_{};
|
||||||
std::string esphome_version{};
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
std::string compilation_time{};
|
StringRef mac_address_ref_{};
|
||||||
std::string model{};
|
void set_mac_address(const StringRef &ref) { this->mac_address_ref_ = ref; }
|
||||||
|
StringRef esphome_version_ref_{};
|
||||||
|
void set_esphome_version(const StringRef &ref) { this->esphome_version_ref_ = ref; }
|
||||||
|
StringRef compilation_time_ref_{};
|
||||||
|
void set_compilation_time(const StringRef &ref) { this->compilation_time_ref_ = ref; }
|
||||||
|
StringRef model_ref_{};
|
||||||
|
void set_model(const StringRef &ref) { this->model_ref_ = ref; }
|
||||||
#ifdef USE_DEEP_SLEEP
|
#ifdef USE_DEEP_SLEEP
|
||||||
bool has_deep_sleep{false};
|
bool has_deep_sleep{false};
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESPHOME_PROJECT_NAME
|
#ifdef ESPHOME_PROJECT_NAME
|
||||||
std::string project_name{};
|
StringRef project_name_ref_{};
|
||||||
|
void set_project_name(const StringRef &ref) { this->project_name_ref_ = ref; }
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESPHOME_PROJECT_NAME
|
#ifdef ESPHOME_PROJECT_NAME
|
||||||
std::string project_version{};
|
StringRef project_version_ref_{};
|
||||||
|
void set_project_version(const StringRef &ref) { this->project_version_ref_ = ref; }
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
uint32_t webserver_port{0};
|
uint32_t webserver_port{0};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
|
||||||
uint32_t legacy_bluetooth_proxy_version{0};
|
|
||||||
#endif
|
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
uint32_t bluetooth_proxy_feature_flags{0};
|
uint32_t bluetooth_proxy_feature_flags{0};
|
||||||
#endif
|
#endif
|
||||||
std::string manufacturer{};
|
StringRef manufacturer_ref_{};
|
||||||
std::string friendly_name{};
|
void set_manufacturer(const StringRef &ref) { this->manufacturer_ref_ = ref; }
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
StringRef friendly_name_ref_{};
|
||||||
uint32_t legacy_voice_assistant_version{0};
|
void set_friendly_name(const StringRef &ref) { this->friendly_name_ref_ = ref; }
|
||||||
#endif
|
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
uint32_t voice_assistant_feature_flags{0};
|
uint32_t voice_assistant_feature_flags{0};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
std::string suggested_area{};
|
StringRef suggested_area_ref_{};
|
||||||
|
void set_suggested_area(const StringRef &ref) { this->suggested_area_ref_ = ref; }
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
std::string bluetooth_mac_address{};
|
StringRef bluetooth_mac_address_ref_{};
|
||||||
|
void set_bluetooth_mac_address(const StringRef &ref) { this->bluetooth_mac_address_ref_ = ref; }
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool api_encryption_supported{false};
|
bool api_encryption_supported{false};
|
||||||
@ -586,7 +593,8 @@ class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_binary_sensor_response"; }
|
const char *message_name() const override { return "list_entities_binary_sensor_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
bool is_status_binary_sensor{false};
|
bool is_status_binary_sensor{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -625,7 +633,8 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
|||||||
bool assumed_state{false};
|
bool assumed_state{false};
|
||||||
bool supports_position{false};
|
bool supports_position{false};
|
||||||
bool supports_tilt{false};
|
bool supports_tilt{false};
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
bool supports_stop{false};
|
bool supports_stop{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -638,11 +647,10 @@ class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
|||||||
class CoverStateResponse : public StateResponseProtoMessage {
|
class CoverStateResponse : public StateResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 22;
|
static constexpr uint8_t MESSAGE_TYPE = 22;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 23;
|
static constexpr uint8_t ESTIMATED_SIZE = 21;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "cover_state_response"; }
|
const char *message_name() const override { return "cover_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
enums::LegacyCoverState legacy_state{};
|
|
||||||
float position{0.0f};
|
float position{0.0f};
|
||||||
float tilt{0.0f};
|
float tilt{0.0f};
|
||||||
enums::CoverOperation current_operation{};
|
enums::CoverOperation current_operation{};
|
||||||
@ -657,12 +665,10 @@ class CoverStateResponse : public StateResponseProtoMessage {
|
|||||||
class CoverCommandRequest : public CommandProtoMessage {
|
class CoverCommandRequest : public CommandProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 30;
|
static constexpr uint8_t MESSAGE_TYPE = 30;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 29;
|
static constexpr uint8_t ESTIMATED_SIZE = 25;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "cover_command_request"; }
|
const char *message_name() const override { return "cover_command_request"; }
|
||||||
#endif
|
#endif
|
||||||
bool has_legacy_command{false};
|
|
||||||
enums::LegacyCoverCommand legacy_command{};
|
|
||||||
bool has_position{false};
|
bool has_position{false};
|
||||||
float position{0.0f};
|
float position{0.0f};
|
||||||
bool has_tilt{false};
|
bool has_tilt{false};
|
||||||
@ -701,16 +707,16 @@ class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
|||||||
class FanStateResponse : public StateResponseProtoMessage {
|
class FanStateResponse : public StateResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 23;
|
static constexpr uint8_t MESSAGE_TYPE = 23;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 30;
|
static constexpr uint8_t ESTIMATED_SIZE = 28;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "fan_state_response"; }
|
const char *message_name() const override { return "fan_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
bool state{false};
|
bool state{false};
|
||||||
bool oscillating{false};
|
bool oscillating{false};
|
||||||
enums::FanSpeed speed{};
|
|
||||||
enums::FanDirection direction{};
|
enums::FanDirection direction{};
|
||||||
int32_t speed_level{0};
|
int32_t speed_level{0};
|
||||||
std::string preset_mode{};
|
StringRef preset_mode_ref_{};
|
||||||
|
void set_preset_mode(const StringRef &ref) { this->preset_mode_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -722,14 +728,12 @@ class FanStateResponse : public StateResponseProtoMessage {
|
|||||||
class FanCommandRequest : public CommandProtoMessage {
|
class FanCommandRequest : public CommandProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 31;
|
static constexpr uint8_t MESSAGE_TYPE = 31;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 42;
|
static constexpr uint8_t ESTIMATED_SIZE = 38;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "fan_command_request"; }
|
const char *message_name() const override { return "fan_command_request"; }
|
||||||
#endif
|
#endif
|
||||||
bool has_state{false};
|
bool has_state{false};
|
||||||
bool state{false};
|
bool state{false};
|
||||||
bool has_speed{false};
|
|
||||||
enums::FanSpeed speed{};
|
|
||||||
bool has_oscillating{false};
|
bool has_oscillating{false};
|
||||||
bool oscillating{false};
|
bool oscillating{false};
|
||||||
bool has_direction{false};
|
bool has_direction{false};
|
||||||
@ -752,15 +756,11 @@ class FanCommandRequest : public CommandProtoMessage {
|
|||||||
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 15;
|
static constexpr uint8_t MESSAGE_TYPE = 15;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 81;
|
static constexpr uint8_t ESTIMATED_SIZE = 73;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_light_response"; }
|
const char *message_name() const override { return "list_entities_light_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::vector<enums::ColorMode> supported_color_modes{};
|
std::vector<enums::ColorMode> supported_color_modes{};
|
||||||
bool legacy_supports_brightness{false};
|
|
||||||
bool legacy_supports_rgb{false};
|
|
||||||
bool legacy_supports_white_value{false};
|
|
||||||
bool legacy_supports_color_temperature{false};
|
|
||||||
float min_mireds{0.0f};
|
float min_mireds{0.0f};
|
||||||
float max_mireds{0.0f};
|
float max_mireds{0.0f};
|
||||||
std::vector<std::string> effects{};
|
std::vector<std::string> effects{};
|
||||||
@ -790,7 +790,8 @@ class LightStateResponse : public StateResponseProtoMessage {
|
|||||||
float color_temperature{0.0f};
|
float color_temperature{0.0f};
|
||||||
float cold_white{0.0f};
|
float cold_white{0.0f};
|
||||||
float warm_white{0.0f};
|
float warm_white{0.0f};
|
||||||
std::string effect{};
|
StringRef effect_ref_{};
|
||||||
|
void set_effect(const StringRef &ref) { this->effect_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -846,16 +847,17 @@ class LightCommandRequest : public CommandProtoMessage {
|
|||||||
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 16;
|
static constexpr uint8_t MESSAGE_TYPE = 16;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
static constexpr uint8_t ESTIMATED_SIZE = 66;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_sensor_response"; }
|
const char *message_name() const override { return "list_entities_sensor_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string unit_of_measurement{};
|
StringRef unit_of_measurement_ref_{};
|
||||||
|
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
|
||||||
int32_t accuracy_decimals{0};
|
int32_t accuracy_decimals{0};
|
||||||
bool force_update{false};
|
bool force_update{false};
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
enums::SensorStateClass state_class{};
|
enums::SensorStateClass state_class{};
|
||||||
enums::SensorLastResetType legacy_last_reset_type{};
|
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -891,7 +893,8 @@ class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
|||||||
const char *message_name() const override { return "list_entities_switch_response"; }
|
const char *message_name() const override { return "list_entities_switch_response"; }
|
||||||
#endif
|
#endif
|
||||||
bool assumed_state{false};
|
bool assumed_state{false};
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -941,7 +944,8 @@ class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_text_sensor_response"; }
|
const char *message_name() const override { return "list_entities_text_sensor_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -957,7 +961,8 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "text_sensor_state_response"; }
|
const char *message_name() const override { return "text_sensor_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string state{};
|
StringRef state_ref_{};
|
||||||
|
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||||
bool missing_state{false};
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -987,13 +992,17 @@ class SubscribeLogsRequest : public ProtoDecodableMessage {
|
|||||||
class SubscribeLogsResponse : public ProtoMessage {
|
class SubscribeLogsResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 29;
|
static constexpr uint8_t MESSAGE_TYPE = 29;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 13;
|
static constexpr uint8_t ESTIMATED_SIZE = 11;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "subscribe_logs_response"; }
|
const char *message_name() const override { return "subscribe_logs_response"; }
|
||||||
#endif
|
#endif
|
||||||
enums::LogLevel level{};
|
enums::LogLevel level{};
|
||||||
std::string message{};
|
const uint8_t *message_ptr_{nullptr};
|
||||||
bool send_failed{false};
|
size_t message_len_{0};
|
||||||
|
void set_message(const uint8_t *data, size_t len) {
|
||||||
|
this->message_ptr_ = data;
|
||||||
|
this->message_len_ = len;
|
||||||
|
}
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1035,6 +1044,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage {
|
class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 34;
|
static constexpr uint8_t MESSAGE_TYPE = 34;
|
||||||
@ -1050,7 +1060,8 @@ class SubscribeHomeassistantServicesRequest : public ProtoDecodableMessage {
|
|||||||
};
|
};
|
||||||
class HomeassistantServiceMap : public ProtoMessage {
|
class HomeassistantServiceMap : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string key{};
|
StringRef key_ref_{};
|
||||||
|
void set_key(const StringRef &ref) { this->key_ref_ = ref; }
|
||||||
std::string value{};
|
std::string value{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1067,7 +1078,8 @@ class HomeassistantServiceResponse : public ProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "homeassistant_service_response"; }
|
const char *message_name() const override { return "homeassistant_service_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string service{};
|
StringRef service_ref_{};
|
||||||
|
void set_service(const StringRef &ref) { this->service_ref_ = ref; }
|
||||||
std::vector<HomeassistantServiceMap> data{};
|
std::vector<HomeassistantServiceMap> data{};
|
||||||
std::vector<HomeassistantServiceMap> data_template{};
|
std::vector<HomeassistantServiceMap> data_template{};
|
||||||
std::vector<HomeassistantServiceMap> variables{};
|
std::vector<HomeassistantServiceMap> variables{};
|
||||||
@ -1080,6 +1092,8 @@ class HomeassistantServiceResponse : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
class SubscribeHomeAssistantStatesRequest : public ProtoDecodableMessage {
|
class SubscribeHomeAssistantStatesRequest : public ProtoDecodableMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 38;
|
static constexpr uint8_t MESSAGE_TYPE = 38;
|
||||||
@ -1100,8 +1114,10 @@ class SubscribeHomeAssistantStateResponse : public ProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
|
const char *message_name() const override { return "subscribe_home_assistant_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string entity_id{};
|
StringRef entity_id_ref_{};
|
||||||
std::string attribute{};
|
void set_entity_id(const StringRef &ref) { this->entity_id_ref_ = ref; }
|
||||||
|
StringRef attribute_ref_{};
|
||||||
|
void set_attribute(const StringRef &ref) { this->attribute_ref_ = ref; }
|
||||||
bool once{false};
|
bool once{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1128,6 +1144,7 @@ class HomeAssistantStateResponse : public ProtoDecodableMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
class GetTimeRequest : public ProtoDecodableMessage {
|
class GetTimeRequest : public ProtoDecodableMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 36;
|
static constexpr uint8_t MESSAGE_TYPE = 36;
|
||||||
@ -1161,7 +1178,8 @@ class GetTimeResponse : public ProtoDecodableMessage {
|
|||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
class ListEntitiesServicesArgument : public ProtoMessage {
|
class ListEntitiesServicesArgument : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string name{};
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
enums::ServiceArgType type{};
|
enums::ServiceArgType type{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1178,7 +1196,8 @@ class ListEntitiesServicesResponse : public ProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_services_response"; }
|
const char *message_name() const override { return "list_entities_services_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string name{};
|
StringRef name_ref_{};
|
||||||
|
void set_name(const StringRef &ref) { this->name_ref_ = ref; }
|
||||||
uint32_t key{0};
|
uint32_t key{0};
|
||||||
std::vector<ListEntitiesServicesArgument> args{};
|
std::vector<ListEntitiesServicesArgument> args{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
@ -1250,7 +1269,12 @@ class CameraImageResponse : public StateResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "camera_image_response"; }
|
const char *message_name() const override { return "camera_image_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string data{};
|
const uint8_t *data_ptr_{nullptr};
|
||||||
|
size_t data_len_{0};
|
||||||
|
void set_data(const uint8_t *data, size_t len) {
|
||||||
|
this->data_ptr_ = data;
|
||||||
|
this->data_len_ = len;
|
||||||
|
}
|
||||||
bool done{false};
|
bool done{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1281,7 +1305,7 @@ class CameraImageRequest : public ProtoDecodableMessage {
|
|||||||
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 46;
|
static constexpr uint8_t MESSAGE_TYPE = 46;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 147;
|
static constexpr uint8_t ESTIMATED_SIZE = 145;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_climate_response"; }
|
const char *message_name() const override { return "list_entities_climate_response"; }
|
||||||
#endif
|
#endif
|
||||||
@ -1291,7 +1315,6 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
|||||||
float visual_min_temperature{0.0f};
|
float visual_min_temperature{0.0f};
|
||||||
float visual_max_temperature{0.0f};
|
float visual_max_temperature{0.0f};
|
||||||
float visual_target_temperature_step{0.0f};
|
float visual_target_temperature_step{0.0f};
|
||||||
bool legacy_supports_away{false};
|
|
||||||
bool supports_action{false};
|
bool supports_action{false};
|
||||||
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
std::vector<enums::ClimateFanMode> supported_fan_modes{};
|
||||||
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
|
std::vector<enums::ClimateSwingMode> supported_swing_modes{};
|
||||||
@ -1314,7 +1337,7 @@ class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
|||||||
class ClimateStateResponse : public StateResponseProtoMessage {
|
class ClimateStateResponse : public StateResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 47;
|
static constexpr uint8_t MESSAGE_TYPE = 47;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 70;
|
static constexpr uint8_t ESTIMATED_SIZE = 68;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "climate_state_response"; }
|
const char *message_name() const override { return "climate_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
@ -1323,13 +1346,14 @@ class ClimateStateResponse : public StateResponseProtoMessage {
|
|||||||
float target_temperature{0.0f};
|
float target_temperature{0.0f};
|
||||||
float target_temperature_low{0.0f};
|
float target_temperature_low{0.0f};
|
||||||
float target_temperature_high{0.0f};
|
float target_temperature_high{0.0f};
|
||||||
bool unused_legacy_away{false};
|
|
||||||
enums::ClimateAction action{};
|
enums::ClimateAction action{};
|
||||||
enums::ClimateFanMode fan_mode{};
|
enums::ClimateFanMode fan_mode{};
|
||||||
enums::ClimateSwingMode swing_mode{};
|
enums::ClimateSwingMode swing_mode{};
|
||||||
std::string custom_fan_mode{};
|
StringRef custom_fan_mode_ref_{};
|
||||||
|
void set_custom_fan_mode(const StringRef &ref) { this->custom_fan_mode_ref_ = ref; }
|
||||||
enums::ClimatePreset preset{};
|
enums::ClimatePreset preset{};
|
||||||
std::string custom_preset{};
|
StringRef custom_preset_ref_{};
|
||||||
|
void set_custom_preset(const StringRef &ref) { this->custom_preset_ref_ = ref; }
|
||||||
float current_humidity{0.0f};
|
float current_humidity{0.0f};
|
||||||
float target_humidity{0.0f};
|
float target_humidity{0.0f};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
@ -1343,7 +1367,7 @@ class ClimateStateResponse : public StateResponseProtoMessage {
|
|||||||
class ClimateCommandRequest : public CommandProtoMessage {
|
class ClimateCommandRequest : public CommandProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 48;
|
static constexpr uint8_t MESSAGE_TYPE = 48;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 88;
|
static constexpr uint8_t ESTIMATED_SIZE = 84;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "climate_command_request"; }
|
const char *message_name() const override { return "climate_command_request"; }
|
||||||
#endif
|
#endif
|
||||||
@ -1355,8 +1379,6 @@ class ClimateCommandRequest : public CommandProtoMessage {
|
|||||||
float target_temperature_low{0.0f};
|
float target_temperature_low{0.0f};
|
||||||
bool has_target_temperature_high{false};
|
bool has_target_temperature_high{false};
|
||||||
float target_temperature_high{0.0f};
|
float target_temperature_high{0.0f};
|
||||||
bool unused_has_legacy_away{false};
|
|
||||||
bool unused_legacy_away{false};
|
|
||||||
bool has_fan_mode{false};
|
bool has_fan_mode{false};
|
||||||
enums::ClimateFanMode fan_mode{};
|
enums::ClimateFanMode fan_mode{};
|
||||||
bool has_swing_mode{false};
|
bool has_swing_mode{false};
|
||||||
@ -1390,9 +1412,11 @@ class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
|||||||
float min_value{0.0f};
|
float min_value{0.0f};
|
||||||
float max_value{0.0f};
|
float max_value{0.0f};
|
||||||
float step{0.0f};
|
float step{0.0f};
|
||||||
std::string unit_of_measurement{};
|
StringRef unit_of_measurement_ref_{};
|
||||||
|
void set_unit_of_measurement(const StringRef &ref) { this->unit_of_measurement_ref_ = ref; }
|
||||||
enums::NumberMode mode{};
|
enums::NumberMode mode{};
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1459,7 +1483,8 @@ class SelectStateResponse : public StateResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "select_state_response"; }
|
const char *message_name() const override { return "select_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string state{};
|
StringRef state_ref_{};
|
||||||
|
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||||
bool missing_state{false};
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1558,7 +1583,8 @@ class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
|||||||
bool assumed_state{false};
|
bool assumed_state{false};
|
||||||
bool supports_open{false};
|
bool supports_open{false};
|
||||||
bool requires_code{false};
|
bool requires_code{false};
|
||||||
std::string code_format{};
|
StringRef code_format_ref_{};
|
||||||
|
void set_code_format(const StringRef &ref) { this->code_format_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1611,7 +1637,8 @@ class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_button_response"; }
|
const char *message_name() const override { return "list_entities_button_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1639,7 +1666,8 @@ class ButtonCommandRequest : public CommandProtoMessage {
|
|||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
class MediaPlayerSupportedFormat : public ProtoMessage {
|
class MediaPlayerSupportedFormat : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string format{};
|
StringRef format_ref_{};
|
||||||
|
void set_format(const StringRef &ref) { this->format_ref_ = ref; }
|
||||||
uint32_t sample_rate{0};
|
uint32_t sample_rate{0};
|
||||||
uint32_t num_channels{0};
|
uint32_t num_channels{0};
|
||||||
enums::MediaPlayerFormatPurpose purpose{};
|
enums::MediaPlayerFormatPurpose purpose{};
|
||||||
@ -1728,47 +1756,13 @@ class SubscribeBluetoothLEAdvertisementsRequest : public ProtoDecodableMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
class BluetoothServiceData : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
std::string uuid{};
|
|
||||||
std::vector<uint32_t> legacy_data{};
|
|
||||||
std::string data{};
|
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
void dump_to(std::string &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class BluetoothLEAdvertisementResponse : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 67;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 107;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "bluetooth_le_advertisement_response"; }
|
|
||||||
#endif
|
|
||||||
uint64_t address{0};
|
|
||||||
std::string name{};
|
|
||||||
int32_t rssi{0};
|
|
||||||
std::vector<std::string> service_uuids{};
|
|
||||||
std::vector<BluetoothServiceData> service_data{};
|
|
||||||
std::vector<BluetoothServiceData> manufacturer_data{};
|
|
||||||
uint32_t address_type{0};
|
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
void dump_to(std::string &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class BluetoothLERawAdvertisement : public ProtoMessage {
|
class BluetoothLERawAdvertisement : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
uint64_t address{0};
|
uint64_t address{0};
|
||||||
int32_t rssi{0};
|
int32_t rssi{0};
|
||||||
uint32_t address_type{0};
|
uint32_t address_type{0};
|
||||||
std::string data{};
|
uint8_t data[62]{};
|
||||||
|
uint8_t data_len{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1847,7 +1841,7 @@ class BluetoothGATTGetServicesRequest : public ProtoDecodableMessage {
|
|||||||
};
|
};
|
||||||
class BluetoothGATTDescriptor : public ProtoMessage {
|
class BluetoothGATTDescriptor : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::vector<uint64_t> uuid{};
|
std::array<uint64_t, 2> uuid{};
|
||||||
uint32_t handle{0};
|
uint32_t handle{0};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -1859,7 +1853,7 @@ class BluetoothGATTDescriptor : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class BluetoothGATTCharacteristic : public ProtoMessage {
|
class BluetoothGATTCharacteristic : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::vector<uint64_t> uuid{};
|
std::array<uint64_t, 2> uuid{};
|
||||||
uint32_t handle{0};
|
uint32_t handle{0};
|
||||||
uint32_t properties{0};
|
uint32_t properties{0};
|
||||||
std::vector<BluetoothGATTDescriptor> descriptors{};
|
std::vector<BluetoothGATTDescriptor> descriptors{};
|
||||||
@ -1873,7 +1867,7 @@ class BluetoothGATTCharacteristic : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class BluetoothGATTService : public ProtoMessage {
|
class BluetoothGATTService : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::vector<uint64_t> uuid{};
|
std::array<uint64_t, 2> uuid{};
|
||||||
uint32_t handle{0};
|
uint32_t handle{0};
|
||||||
std::vector<BluetoothGATTCharacteristic> characteristics{};
|
std::vector<BluetoothGATTCharacteristic> characteristics{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
@ -1887,12 +1881,12 @@ class BluetoothGATTService : public ProtoMessage {
|
|||||||
class BluetoothGATTGetServicesResponse : public ProtoMessage {
|
class BluetoothGATTGetServicesResponse : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 71;
|
static constexpr uint8_t MESSAGE_TYPE = 71;
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 38;
|
static constexpr uint8_t ESTIMATED_SIZE = 21;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "bluetooth_gatt_get_services_response"; }
|
const char *message_name() const override { return "bluetooth_gatt_get_services_response"; }
|
||||||
#endif
|
#endif
|
||||||
uint64_t address{0};
|
uint64_t address{0};
|
||||||
std::vector<BluetoothGATTService> services{};
|
std::array<BluetoothGATTService, 1> services{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -1942,7 +1936,12 @@ class BluetoothGATTReadResponse : public ProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
uint64_t address{0};
|
uint64_t address{0};
|
||||||
uint32_t handle{0};
|
uint32_t handle{0};
|
||||||
std::string data{};
|
const uint8_t *data_ptr_{nullptr};
|
||||||
|
size_t data_len_{0};
|
||||||
|
void set_data(const uint8_t *data, size_t len) {
|
||||||
|
this->data_ptr_ = data;
|
||||||
|
this->data_len_ = len;
|
||||||
|
}
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2030,7 +2029,12 @@ class BluetoothGATTNotifyDataResponse : public ProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
uint64_t address{0};
|
uint64_t address{0};
|
||||||
uint32_t handle{0};
|
uint32_t handle{0};
|
||||||
std::string data{};
|
const uint8_t *data_ptr_{nullptr};
|
||||||
|
size_t data_len_{0};
|
||||||
|
void set_data(const uint8_t *data, size_t len) {
|
||||||
|
this->data_ptr_ = data;
|
||||||
|
this->data_len_ = len;
|
||||||
|
}
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2260,10 +2264,12 @@ class VoiceAssistantRequest : public ProtoMessage {
|
|||||||
const char *message_name() const override { return "voice_assistant_request"; }
|
const char *message_name() const override { return "voice_assistant_request"; }
|
||||||
#endif
|
#endif
|
||||||
bool start{false};
|
bool start{false};
|
||||||
std::string conversation_id{};
|
StringRef conversation_id_ref_{};
|
||||||
|
void set_conversation_id(const StringRef &ref) { this->conversation_id_ref_ = ref; }
|
||||||
uint32_t flags{0};
|
uint32_t flags{0};
|
||||||
VoiceAssistantAudioSettings audio_settings{};
|
VoiceAssistantAudioSettings audio_settings{};
|
||||||
std::string wake_word_phrase{};
|
StringRef wake_word_phrase_ref_{};
|
||||||
|
void set_wake_word_phrase(const StringRef &ref) { this->wake_word_phrase_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2324,6 +2330,12 @@ class VoiceAssistantAudio : public ProtoDecodableMessage {
|
|||||||
const char *message_name() const override { return "voice_assistant_audio"; }
|
const char *message_name() const override { return "voice_assistant_audio"; }
|
||||||
#endif
|
#endif
|
||||||
std::string data{};
|
std::string data{};
|
||||||
|
const uint8_t *data_ptr_{nullptr};
|
||||||
|
size_t data_len_{0};
|
||||||
|
void set_data(const uint8_t *data, size_t len) {
|
||||||
|
this->data_ptr_ = data;
|
||||||
|
this->data_len_ = len;
|
||||||
|
}
|
||||||
bool end{false};
|
bool end{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -2393,8 +2405,10 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
class VoiceAssistantWakeWord : public ProtoMessage {
|
class VoiceAssistantWakeWord : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string id{};
|
StringRef id_ref_{};
|
||||||
std::string wake_word{};
|
void set_id(const StringRef &ref) { this->id_ref_ = ref; }
|
||||||
|
StringRef wake_word_ref_{};
|
||||||
|
void set_wake_word(const StringRef &ref) { this->wake_word_ref_ = ref; }
|
||||||
std::vector<std::string> trained_languages{};
|
std::vector<std::string> trained_languages{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -2515,7 +2529,8 @@ class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
|||||||
#endif
|
#endif
|
||||||
uint32_t min_length{0};
|
uint32_t min_length{0};
|
||||||
uint32_t max_length{0};
|
uint32_t max_length{0};
|
||||||
std::string pattern{};
|
StringRef pattern_ref_{};
|
||||||
|
void set_pattern(const StringRef &ref) { this->pattern_ref_ = ref; }
|
||||||
enums::TextMode mode{};
|
enums::TextMode mode{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -2532,7 +2547,8 @@ class TextStateResponse : public StateResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "text_state_response"; }
|
const char *message_name() const override { return "text_state_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string state{};
|
StringRef state_ref_{};
|
||||||
|
void set_state(const StringRef &ref) { this->state_ref_ = ref; }
|
||||||
bool missing_state{false};
|
bool missing_state{false};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -2676,7 +2692,8 @@ class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_event_response"; }
|
const char *message_name() const override { return "list_entities_event_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
std::vector<std::string> event_types{};
|
std::vector<std::string> event_types{};
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
@ -2693,7 +2710,8 @@ class EventResponse : public StateResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "event_response"; }
|
const char *message_name() const override { return "event_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string event_type{};
|
StringRef event_type_ref_{};
|
||||||
|
void set_event_type(const StringRef &ref) { this->event_type_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2711,7 +2729,8 @@ class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_valve_response"; }
|
const char *message_name() const override { return "list_entities_valve_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
bool assumed_state{false};
|
bool assumed_state{false};
|
||||||
bool supports_position{false};
|
bool supports_position{false};
|
||||||
bool supports_stop{false};
|
bool supports_stop{false};
|
||||||
@ -2817,7 +2836,8 @@ class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
|||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
const char *message_name() const override { return "list_entities_update_response"; }
|
const char *message_name() const override { return "list_entities_update_response"; }
|
||||||
#endif
|
#endif
|
||||||
std::string device_class{};
|
StringRef device_class_ref_{};
|
||||||
|
void set_device_class(const StringRef &ref) { this->device_class_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2837,11 +2857,16 @@ class UpdateStateResponse : public StateResponseProtoMessage {
|
|||||||
bool in_progress{false};
|
bool in_progress{false};
|
||||||
bool has_progress{false};
|
bool has_progress{false};
|
||||||
float progress{0.0f};
|
float progress{0.0f};
|
||||||
std::string current_version{};
|
StringRef current_version_ref_{};
|
||||||
std::string latest_version{};
|
void set_current_version(const StringRef &ref) { this->current_version_ref_ = ref; }
|
||||||
std::string title{};
|
StringRef latest_version_ref_{};
|
||||||
std::string release_summary{};
|
void set_latest_version(const StringRef &ref) { this->latest_version_ref_ = ref; }
|
||||||
std::string release_url{};
|
StringRef title_ref_{};
|
||||||
|
void set_title(const StringRef &ref) { this->title_ref_ = ref; }
|
||||||
|
StringRef release_summary_ref_{};
|
||||||
|
void set_release_summary(const StringRef &ref) { this->release_summary_ref_ = ref; }
|
||||||
|
StringRef release_url_ref_{};
|
||||||
|
void set_release_url(const StringRef &ref) { this->release_url_ref_ = ref; }
|
||||||
void encode(ProtoWriteBuffer buffer) const override;
|
void encode(ProtoWriteBuffer buffer) const override;
|
||||||
void calculate_size(uint32_t &total_size) const override;
|
void calculate_size(uint32_t &total_size) const override;
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -2868,5 +2893,4 @@ class UpdateCommandRequest : public CommandProtoMessage {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,8 +3,7 @@
|
|||||||
#include "api_pb2_service.h"
|
#include "api_pb2_service.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
static const char *const TAG = "api.service";
|
static const char *const TAG = "api.service";
|
||||||
|
|
||||||
@ -16,7 +15,7 @@ void APIServerConnectionBase::log_send_message_(const char *name, const std::str
|
|||||||
|
|
||||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
case 1: {
|
case HelloRequest::MESSAGE_TYPE: {
|
||||||
HelloRequest msg;
|
HelloRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -25,7 +24,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_hello_request(msg);
|
this->on_hello_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 3: {
|
case ConnectRequest::MESSAGE_TYPE: {
|
||||||
ConnectRequest msg;
|
ConnectRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -34,7 +33,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_connect_request(msg);
|
this->on_connect_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 5: {
|
case DisconnectRequest::MESSAGE_TYPE: {
|
||||||
DisconnectRequest msg;
|
DisconnectRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -43,7 +42,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_disconnect_request(msg);
|
this->on_disconnect_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 6: {
|
case DisconnectResponse::MESSAGE_TYPE: {
|
||||||
DisconnectResponse msg;
|
DisconnectResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -52,7 +51,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_disconnect_response(msg);
|
this->on_disconnect_response(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 7: {
|
case PingRequest::MESSAGE_TYPE: {
|
||||||
PingRequest msg;
|
PingRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -61,7 +60,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_ping_request(msg);
|
this->on_ping_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 8: {
|
case PingResponse::MESSAGE_TYPE: {
|
||||||
PingResponse msg;
|
PingResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -70,7 +69,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_ping_response(msg);
|
this->on_ping_response(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 9: {
|
case DeviceInfoRequest::MESSAGE_TYPE: {
|
||||||
DeviceInfoRequest msg;
|
DeviceInfoRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -79,7 +78,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_device_info_request(msg);
|
this->on_device_info_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 11: {
|
case ListEntitiesRequest::MESSAGE_TYPE: {
|
||||||
ListEntitiesRequest msg;
|
ListEntitiesRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -88,7 +87,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_list_entities_request(msg);
|
this->on_list_entities_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 20: {
|
case SubscribeStatesRequest::MESSAGE_TYPE: {
|
||||||
SubscribeStatesRequest msg;
|
SubscribeStatesRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -97,7 +96,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_subscribe_states_request(msg);
|
this->on_subscribe_states_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 28: {
|
case SubscribeLogsRequest::MESSAGE_TYPE: {
|
||||||
SubscribeLogsRequest msg;
|
SubscribeLogsRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -107,7 +106,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#ifdef USE_COVER
|
#ifdef USE_COVER
|
||||||
case 30: {
|
case CoverCommandRequest::MESSAGE_TYPE: {
|
||||||
CoverCommandRequest msg;
|
CoverCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -118,7 +117,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_FAN
|
#ifdef USE_FAN
|
||||||
case 31: {
|
case FanCommandRequest::MESSAGE_TYPE: {
|
||||||
FanCommandRequest msg;
|
FanCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -129,7 +128,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
case 32: {
|
case LightCommandRequest::MESSAGE_TYPE: {
|
||||||
LightCommandRequest msg;
|
LightCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -140,7 +139,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
case 33: {
|
case SwitchCommandRequest::MESSAGE_TYPE: {
|
||||||
SwitchCommandRequest msg;
|
SwitchCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -150,7 +149,8 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
case 34: {
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
|
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
|
||||||
SubscribeHomeassistantServicesRequest msg;
|
SubscribeHomeassistantServicesRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -159,7 +159,8 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_subscribe_homeassistant_services_request(msg);
|
this->on_subscribe_homeassistant_services_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 36: {
|
#endif
|
||||||
|
case GetTimeRequest::MESSAGE_TYPE: {
|
||||||
GetTimeRequest msg;
|
GetTimeRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -168,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_get_time_request(msg);
|
this->on_get_time_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 37: {
|
case GetTimeResponse::MESSAGE_TYPE: {
|
||||||
GetTimeResponse msg;
|
GetTimeResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -177,7 +178,8 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_get_time_response(msg);
|
this->on_get_time_response(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 38: {
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
|
||||||
SubscribeHomeAssistantStatesRequest msg;
|
SubscribeHomeAssistantStatesRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -186,7 +188,9 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_subscribe_home_assistant_states_request(msg);
|
this->on_subscribe_home_assistant_states_request(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 40: {
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
|
case HomeAssistantStateResponse::MESSAGE_TYPE: {
|
||||||
HomeAssistantStateResponse msg;
|
HomeAssistantStateResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -195,8 +199,9 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_home_assistant_state_response(msg);
|
this->on_home_assistant_state_response(msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
case 42: {
|
case ExecuteServiceRequest::MESSAGE_TYPE: {
|
||||||
ExecuteServiceRequest msg;
|
ExecuteServiceRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -207,7 +212,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CAMERA
|
#ifdef USE_CAMERA
|
||||||
case 45: {
|
case CameraImageRequest::MESSAGE_TYPE: {
|
||||||
CameraImageRequest msg;
|
CameraImageRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -218,7 +223,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_CLIMATE
|
#ifdef USE_CLIMATE
|
||||||
case 48: {
|
case ClimateCommandRequest::MESSAGE_TYPE: {
|
||||||
ClimateCommandRequest msg;
|
ClimateCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -229,7 +234,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
case 51: {
|
case NumberCommandRequest::MESSAGE_TYPE: {
|
||||||
NumberCommandRequest msg;
|
NumberCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -240,7 +245,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
case 54: {
|
case SelectCommandRequest::MESSAGE_TYPE: {
|
||||||
SelectCommandRequest msg;
|
SelectCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -251,7 +256,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SIREN
|
#ifdef USE_SIREN
|
||||||
case 57: {
|
case SirenCommandRequest::MESSAGE_TYPE: {
|
||||||
SirenCommandRequest msg;
|
SirenCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -262,7 +267,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_LOCK
|
#ifdef USE_LOCK
|
||||||
case 60: {
|
case LockCommandRequest::MESSAGE_TYPE: {
|
||||||
LockCommandRequest msg;
|
LockCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -273,7 +278,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BUTTON
|
#ifdef USE_BUTTON
|
||||||
case 62: {
|
case ButtonCommandRequest::MESSAGE_TYPE: {
|
||||||
ButtonCommandRequest msg;
|
ButtonCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -284,7 +289,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
case 65: {
|
case MediaPlayerCommandRequest::MESSAGE_TYPE: {
|
||||||
MediaPlayerCommandRequest msg;
|
MediaPlayerCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -295,7 +300,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 66: {
|
case SubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
|
||||||
SubscribeBluetoothLEAdvertisementsRequest msg;
|
SubscribeBluetoothLEAdvertisementsRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -306,7 +311,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 68: {
|
case BluetoothDeviceRequest::MESSAGE_TYPE: {
|
||||||
BluetoothDeviceRequest msg;
|
BluetoothDeviceRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -317,7 +322,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 70: {
|
case BluetoothGATTGetServicesRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTGetServicesRequest msg;
|
BluetoothGATTGetServicesRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -328,7 +333,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 73: {
|
case BluetoothGATTReadRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTReadRequest msg;
|
BluetoothGATTReadRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -339,7 +344,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 75: {
|
case BluetoothGATTWriteRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTWriteRequest msg;
|
BluetoothGATTWriteRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -350,7 +355,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 76: {
|
case BluetoothGATTReadDescriptorRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTReadDescriptorRequest msg;
|
BluetoothGATTReadDescriptorRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -361,7 +366,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 77: {
|
case BluetoothGATTWriteDescriptorRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTWriteDescriptorRequest msg;
|
BluetoothGATTWriteDescriptorRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -372,7 +377,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 78: {
|
case BluetoothGATTNotifyRequest::MESSAGE_TYPE: {
|
||||||
BluetoothGATTNotifyRequest msg;
|
BluetoothGATTNotifyRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -383,7 +388,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 80: {
|
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
|
||||||
SubscribeBluetoothConnectionsFreeRequest msg;
|
SubscribeBluetoothConnectionsFreeRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -394,7 +399,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 87: {
|
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
|
||||||
UnsubscribeBluetoothLEAdvertisementsRequest msg;
|
UnsubscribeBluetoothLEAdvertisementsRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -405,7 +410,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 89: {
|
case SubscribeVoiceAssistantRequest::MESSAGE_TYPE: {
|
||||||
SubscribeVoiceAssistantRequest msg;
|
SubscribeVoiceAssistantRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -416,7 +421,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 91: {
|
case VoiceAssistantResponse::MESSAGE_TYPE: {
|
||||||
VoiceAssistantResponse msg;
|
VoiceAssistantResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -427,7 +432,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 92: {
|
case VoiceAssistantEventResponse::MESSAGE_TYPE: {
|
||||||
VoiceAssistantEventResponse msg;
|
VoiceAssistantEventResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -438,7 +443,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ALARM_CONTROL_PANEL
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
case 96: {
|
case AlarmControlPanelCommandRequest::MESSAGE_TYPE: {
|
||||||
AlarmControlPanelCommandRequest msg;
|
AlarmControlPanelCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -449,7 +454,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT
|
#ifdef USE_TEXT
|
||||||
case 99: {
|
case TextCommandRequest::MESSAGE_TYPE: {
|
||||||
TextCommandRequest msg;
|
TextCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -460,7 +465,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATE
|
#ifdef USE_DATETIME_DATE
|
||||||
case 102: {
|
case DateCommandRequest::MESSAGE_TYPE: {
|
||||||
DateCommandRequest msg;
|
DateCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -471,7 +476,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_TIME
|
#ifdef USE_DATETIME_TIME
|
||||||
case 105: {
|
case TimeCommandRequest::MESSAGE_TYPE: {
|
||||||
TimeCommandRequest msg;
|
TimeCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -482,7 +487,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 106: {
|
case VoiceAssistantAudio::MESSAGE_TYPE: {
|
||||||
VoiceAssistantAudio msg;
|
VoiceAssistantAudio msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -493,7 +498,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VALVE
|
#ifdef USE_VALVE
|
||||||
case 111: {
|
case ValveCommandRequest::MESSAGE_TYPE: {
|
||||||
ValveCommandRequest msg;
|
ValveCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -504,7 +509,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_DATETIME_DATETIME
|
#ifdef USE_DATETIME_DATETIME
|
||||||
case 114: {
|
case DateTimeCommandRequest::MESSAGE_TYPE: {
|
||||||
DateTimeCommandRequest msg;
|
DateTimeCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -515,7 +520,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 115: {
|
case VoiceAssistantTimerEventResponse::MESSAGE_TYPE: {
|
||||||
VoiceAssistantTimerEventResponse msg;
|
VoiceAssistantTimerEventResponse msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -526,7 +531,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
case 118: {
|
case UpdateCommandRequest::MESSAGE_TYPE: {
|
||||||
UpdateCommandRequest msg;
|
UpdateCommandRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -537,7 +542,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 119: {
|
case VoiceAssistantAnnounceRequest::MESSAGE_TYPE: {
|
||||||
VoiceAssistantAnnounceRequest msg;
|
VoiceAssistantAnnounceRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -548,7 +553,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 121: {
|
case VoiceAssistantConfigurationRequest::MESSAGE_TYPE: {
|
||||||
VoiceAssistantConfigurationRequest msg;
|
VoiceAssistantConfigurationRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -559,7 +564,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
case 123: {
|
case VoiceAssistantSetConfiguration::MESSAGE_TYPE: {
|
||||||
VoiceAssistantSetConfiguration msg;
|
VoiceAssistantSetConfiguration msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -570,7 +575,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
case 124: {
|
case NoiseEncryptionSetKeyRequest::MESSAGE_TYPE: {
|
||||||
NoiseEncryptionSetKeyRequest msg;
|
NoiseEncryptionSetKeyRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -581,7 +586,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case 127: {
|
case BluetoothScannerSetModeRequest::MESSAGE_TYPE: {
|
||||||
BluetoothScannerSetModeRequest msg;
|
BluetoothScannerSetModeRequest msg;
|
||||||
msg.decode(msg_data, msg_size);
|
msg.decode(msg_data, msg_size);
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
@ -597,35 +602,28 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
void APIServerConnection::on_hello_request(const HelloRequest &msg) {
|
||||||
HelloResponse ret = this->hello(msg);
|
if (!this->send_hello_response(msg)) {
|
||||||
if (!this->send_message(ret, HelloResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
void APIServerConnection::on_connect_request(const ConnectRequest &msg) {
|
||||||
ConnectResponse ret = this->connect(msg);
|
if (!this->send_connect_response(msg)) {
|
||||||
if (!this->send_message(ret, ConnectResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
void APIServerConnection::on_disconnect_request(const DisconnectRequest &msg) {
|
||||||
DisconnectResponse ret = this->disconnect(msg);
|
if (!this->send_disconnect_response(msg)) {
|
||||||
if (!this->send_message(ret, DisconnectResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
void APIServerConnection::on_ping_request(const PingRequest &msg) {
|
||||||
PingResponse ret = this->ping(msg);
|
if (!this->send_ping_response(msg)) {
|
||||||
if (!this->send_message(ret, PingResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
this->on_fatal_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
void APIServerConnection::on_device_info_request(const DeviceInfoRequest &msg) {
|
||||||
if (this->check_connection_setup_()) {
|
if (this->check_connection_setup_() && !this->send_device_info_response(msg)) {
|
||||||
DeviceInfoResponse ret = this->device_info(msg);
|
this->on_fatal_error();
|
||||||
if (!this->send_message(ret, DeviceInfoResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
void APIServerConnection::on_list_entities_request(const ListEntitiesRequest &msg) {
|
||||||
@ -643,23 +641,24 @@ void APIServerConnection::on_subscribe_logs_request(const SubscribeLogsRequest &
|
|||||||
this->subscribe_logs(msg);
|
this->subscribe_logs(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
void APIServerConnection::on_subscribe_homeassistant_services_request(
|
||||||
const SubscribeHomeassistantServicesRequest &msg) {
|
const SubscribeHomeassistantServicesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_()) {
|
||||||
this->subscribe_homeassistant_services(msg);
|
this->subscribe_homeassistant_services(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
void APIServerConnection::on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_()) {
|
||||||
this->subscribe_home_assistant_states(msg);
|
this->subscribe_home_assistant_states(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
|
||||||
if (this->check_connection_setup_()) {
|
if (this->check_connection_setup_() && !this->send_get_time_response(msg)) {
|
||||||
GetTimeResponse ret = this->get_time(msg);
|
this->on_fatal_error();
|
||||||
if (!this->send_message(ret, GetTimeResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
@ -671,11 +670,8 @@ void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_() && !this->send_noise_encryption_set_key_response(msg)) {
|
||||||
NoiseEncryptionSetKeyResponse ret = this->noise_encryption_set_key(msg);
|
this->on_fatal_error();
|
||||||
if (!this->send_message(ret, NoiseEncryptionSetKeyResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -865,11 +861,8 @@ void APIServerConnection::on_bluetooth_gatt_notify_request(const BluetoothGATTNo
|
|||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
void APIServerConnection::on_subscribe_bluetooth_connections_free_request(
|
||||||
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
const SubscribeBluetoothConnectionsFreeRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_() && !this->send_subscribe_bluetooth_connections_free_response(msg)) {
|
||||||
BluetoothConnectionsFreeResponse ret = this->subscribe_bluetooth_connections_free(msg);
|
this->on_fatal_error();
|
||||||
if (!this->send_message(ret, BluetoothConnectionsFreeResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -897,11 +890,8 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo
|
|||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
|
void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) {
|
||||||
if (this->check_authenticated_()) {
|
if (this->check_authenticated_() && !this->send_voice_assistant_get_configuration_response(msg)) {
|
||||||
VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg);
|
this->on_fatal_error();
|
||||||
if (!this->send_message(ret, VoiceAssistantConfigurationResponse::MESSAGE_TYPE)) {
|
|
||||||
this->on_fatal_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -920,5 +910,4 @@ void APIServerConnection::on_alarm_control_panel_command_request(const AlarmCont
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class APIServerConnectionBase : public ProtoService {
|
class APIServerConnectionBase : public ProtoService {
|
||||||
public:
|
public:
|
||||||
@ -61,11 +60,17 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
virtual void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &value){};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
virtual void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &value){};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
virtual void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &value){};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
virtual void on_home_assistant_state_response(const HomeAssistantStateResponse &value){};
|
||||||
|
#endif
|
||||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||||
|
|
||||||
@ -207,22 +212,26 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
|
|
||||||
class APIServerConnection : public APIServerConnectionBase {
|
class APIServerConnection : public APIServerConnectionBase {
|
||||||
public:
|
public:
|
||||||
virtual HelloResponse hello(const HelloRequest &msg) = 0;
|
virtual bool send_hello_response(const HelloRequest &msg) = 0;
|
||||||
virtual ConnectResponse connect(const ConnectRequest &msg) = 0;
|
virtual bool send_connect_response(const ConnectRequest &msg) = 0;
|
||||||
virtual DisconnectResponse disconnect(const DisconnectRequest &msg) = 0;
|
virtual bool send_disconnect_response(const DisconnectRequest &msg) = 0;
|
||||||
virtual PingResponse ping(const PingRequest &msg) = 0;
|
virtual bool send_ping_response(const PingRequest &msg) = 0;
|
||||||
virtual DeviceInfoResponse device_info(const DeviceInfoRequest &msg) = 0;
|
virtual bool send_device_info_response(const DeviceInfoRequest &msg) = 0;
|
||||||
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
virtual void list_entities(const ListEntitiesRequest &msg) = 0;
|
||||||
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
virtual void subscribe_states(const SubscribeStatesRequest &msg) = 0;
|
||||||
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
virtual void subscribe_logs(const SubscribeLogsRequest &msg) = 0;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
#endif
|
||||||
|
virtual bool send_get_time_response(const GetTimeRequest &msg) = 0;
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
|
virtual bool send_noise_encryption_set_key_response(const NoiseEncryptionSetKeyRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BUTTON
|
#ifdef USE_BUTTON
|
||||||
virtual void button_command(const ButtonCommandRequest &msg) = 0;
|
virtual void button_command(const ButtonCommandRequest &msg) = 0;
|
||||||
@ -303,7 +312,7 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
|
virtual void bluetooth_gatt_notify(const BluetoothGATTNotifyRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
virtual BluetoothConnectionsFreeResponse subscribe_bluetooth_connections_free(
|
virtual bool send_subscribe_bluetooth_connections_free_response(
|
||||||
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
|
const SubscribeBluetoothConnectionsFreeRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
@ -316,8 +325,7 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
|
virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration(
|
virtual bool send_voice_assistant_get_configuration_response(const VoiceAssistantConfigurationRequest &msg) = 0;
|
||||||
const VoiceAssistantConfigurationRequest &msg) = 0;
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_VOICE_ASSISTANT
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
|
virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0;
|
||||||
@ -334,8 +342,12 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
void on_list_entities_request(const ListEntitiesRequest &msg) override;
|
||||||
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
void on_subscribe_states_request(const SubscribeStatesRequest &msg) override;
|
||||||
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
void on_subscribe_logs_request(const SubscribeLogsRequest &msg) override;
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||||
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||||
|
#endif
|
||||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||||
@ -445,5 +457,4 @@ class APIServerConnection : public APIServerConnectionBase {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
static const char *const TAG = "api";
|
static const char *const TAG = "api";
|
||||||
|
|
||||||
@ -184,9 +183,9 @@ void APIServer::loop() {
|
|||||||
|
|
||||||
// Rare case: handle disconnection
|
// Rare case: handle disconnection
|
||||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
|
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name.c_str());
|
||||||
|
|
||||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||||
if (client_index < this->clients_.size() - 1) {
|
if (client_index < this->clients_.size() - 1) {
|
||||||
@ -370,12 +369,15 @@ void APIServer::set_password(const std::string &password) { this->password_ = pa
|
|||||||
|
|
||||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||||
for (auto &client : this->clients_) {
|
for (auto &client : this->clients_) {
|
||||||
client->send_homeassistant_service_call(call);
|
client->send_homeassistant_service_call(call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f) {
|
std::function<void(std::string)> f) {
|
||||||
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
this->state_subs_.push_back(HomeAssistantStateSubscription{
|
||||||
@ -399,6 +401,7 @@ void APIServer::get_home_assistant_state(std::string entity_id, optional<std::st
|
|||||||
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
const std::vector<APIServer::HomeAssistantStateSubscription> &APIServer::get_state_subs() const {
|
||||||
return this->state_subs_;
|
return this->state_subs_;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
uint16_t APIServer::get_port() const { return this->port_; }
|
uint16_t APIServer::get_port() const { return this->port_; }
|
||||||
|
|
||||||
@ -483,6 +486,5 @@ bool APIServer::teardown() {
|
|||||||
return this->clients_.empty();
|
return this->clients_.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
struct SavedNoisePsk {
|
struct SavedNoisePsk {
|
||||||
@ -107,7 +106,9 @@ class APIServer : public Component, public Controller {
|
|||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||||
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||||
#endif
|
#endif
|
||||||
@ -127,6 +128,7 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
bool is_connected() const;
|
bool is_connected() const;
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
struct HomeAssistantStateSubscription {
|
struct HomeAssistantStateSubscription {
|
||||||
std::string entity_id;
|
std::string entity_id;
|
||||||
optional<std::string> attribute;
|
optional<std::string> attribute;
|
||||||
@ -139,6 +141,7 @@ class APIServer : public Component, public Controller {
|
|||||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||||
std::function<void(std::string)> f);
|
std::function<void(std::string)> f);
|
||||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||||
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||||
#endif
|
#endif
|
||||||
@ -172,7 +175,9 @@ class APIServer : public Component, public Controller {
|
|||||||
std::string password_;
|
std::string password_;
|
||||||
#endif
|
#endif
|
||||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||||
|
#endif
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
std::vector<UserServiceDescriptor *> user_services_;
|
std::vector<UserServiceDescriptor *> user_services_;
|
||||||
#endif
|
#endif
|
||||||
@ -196,6 +201,5 @@ template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
|||||||
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
bool check(Ts... x) override { return global_api_server->is_connected(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,6 +14,8 @@ with warnings.catch_warnings():
|
|||||||
from aioesphomeapi import APIClient, parse_log_message
|
from aioesphomeapi import APIClient, parse_log_message
|
||||||
from aioesphomeapi.log_runner import async_run
|
from aioesphomeapi.log_runner import async_run
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
|
||||||
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
|
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@ -66,7 +68,5 @@ async def async_run_logs(config: dict[str, Any], address: str) -> None:
|
|||||||
|
|
||||||
def run_logs(config: dict[str, Any], address: str) -> None:
|
def run_logs(config: dict[str, Any], address: str) -> None:
|
||||||
"""Run the logs command."""
|
"""Run the logs command."""
|
||||||
try:
|
with contextlib.suppress(KeyboardInterrupt):
|
||||||
asyncio.run(async_run_logs(config, address))
|
asyncio.run(async_run_logs(config, address))
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#endif
|
#endif
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||||
@ -84,6 +83,7 @@ class CustomAPIDevice {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
@ -135,7 +135,9 @@ class CustomAPIDevice {
|
|||||||
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1);
|
||||||
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
/** Call a Home Assistant service from ESPHome.
|
/** Call a Home Assistant service from ESPHome.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
@ -148,7 +150,7 @@ class CustomAPIDevice {
|
|||||||
*/
|
*/
|
||||||
void call_homeassistant_service(const std::string &service_name) {
|
void call_homeassistant_service(const std::string &service_name) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = service_name;
|
resp.set_service(StringRef(service_name));
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +170,12 @@ class CustomAPIDevice {
|
|||||||
*/
|
*/
|
||||||
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = service_name;
|
resp.set_service(StringRef(service_name));
|
||||||
for (auto &it : data) {
|
for (auto &it : data) {
|
||||||
HomeassistantServiceMap kv;
|
resp.data.emplace_back();
|
||||||
kv.key = it.first;
|
auto &kv = resp.data.back();
|
||||||
|
kv.set_key(StringRef(it.first));
|
||||||
kv.value = it.second;
|
kv.value = it.second;
|
||||||
resp.data.push_back(kv);
|
|
||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
@ -190,7 +192,7 @@ class CustomAPIDevice {
|
|||||||
*/
|
*/
|
||||||
void fire_homeassistant_event(const std::string &event_name) {
|
void fire_homeassistant_event(const std::string &event_name) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = event_name;
|
resp.set_service(StringRef(event_name));
|
||||||
resp.is_event = true;
|
resp.is_event = true;
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
@ -210,18 +212,18 @@ class CustomAPIDevice {
|
|||||||
*/
|
*/
|
||||||
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = service_name;
|
resp.set_service(StringRef(service_name));
|
||||||
resp.is_event = true;
|
resp.is_event = true;
|
||||||
for (auto &it : data) {
|
for (auto &it : data) {
|
||||||
HomeassistantServiceMap kv;
|
resp.data.emplace_back();
|
||||||
kv.key = it.first;
|
auto &kv = resp.data.back();
|
||||||
|
kv.set_key(StringRef(it.first));
|
||||||
kv.value = it.second;
|
kv.value = it.second;
|
||||||
resp.data.push_back(kv);
|
|
||||||
}
|
}
|
||||||
global_api_server->send_homeassistant_service_call(resp);
|
global_api_server->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
#include "api_server.h"
|
#include "api_server.h"
|
||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
||||||
private:
|
private:
|
||||||
@ -36,6 +36,9 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
|||||||
|
|
||||||
template<typename... Ts> class TemplatableKeyValuePair {
|
template<typename... Ts> class TemplatableKeyValuePair {
|
||||||
public:
|
public:
|
||||||
|
// Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
|
||||||
|
// and never templatable values or lambdas. Only the value parameter can be a lambda/template.
|
||||||
|
// Using pass-by-value with std::move allows optimal performance for both lvalues and rvalues.
|
||||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
||||||
std::string key;
|
std::string key;
|
||||||
TemplatableStringValue<Ts...> value;
|
TemplatableStringValue<Ts...> value;
|
||||||
@ -47,37 +50,39 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
|
|
||||||
template<typename T> void set_service(T service) { this->service_ = service; }
|
template<typename T> void set_service(T service) { this->service_ = service; }
|
||||||
|
|
||||||
template<typename T> void add_data(std::string key, T value) {
|
// Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
|
||||||
this->data_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
// The value parameter can be a lambda/template, but keys are never templatable.
|
||||||
}
|
// Using pass-by-value allows the compiler to optimize for both lvalues and rvalues.
|
||||||
|
template<typename T> void add_data(std::string key, T value) { this->data_.emplace_back(std::move(key), value); }
|
||||||
template<typename T> void add_data_template(std::string key, T value) {
|
template<typename T> void add_data_template(std::string key, T value) {
|
||||||
this->data_template_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
this->data_template_.emplace_back(std::move(key), value);
|
||||||
}
|
}
|
||||||
template<typename T> void add_variable(std::string key, T value) {
|
template<typename T> void add_variable(std::string key, T value) {
|
||||||
this->variables_.push_back(TemplatableKeyValuePair<Ts...>(key, value));
|
this->variables_.emplace_back(std::move(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
HomeassistantServiceResponse resp;
|
HomeassistantServiceResponse resp;
|
||||||
resp.service = this->service_.value(x...);
|
std::string service_value = this->service_.value(x...);
|
||||||
|
resp.set_service(StringRef(service_value));
|
||||||
resp.is_event = this->is_event_;
|
resp.is_event = this->is_event_;
|
||||||
for (auto &it : this->data_) {
|
for (auto &it : this->data_) {
|
||||||
HomeassistantServiceMap kv;
|
resp.data.emplace_back();
|
||||||
kv.key = it.key;
|
auto &kv = resp.data.back();
|
||||||
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
resp.data.push_back(kv);
|
|
||||||
}
|
}
|
||||||
for (auto &it : this->data_template_) {
|
for (auto &it : this->data_template_) {
|
||||||
HomeassistantServiceMap kv;
|
resp.data_template.emplace_back();
|
||||||
kv.key = it.key;
|
auto &kv = resp.data_template.back();
|
||||||
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
resp.data_template.push_back(kv);
|
|
||||||
}
|
}
|
||||||
for (auto &it : this->variables_) {
|
for (auto &it : this->variables_) {
|
||||||
HomeassistantServiceMap kv;
|
resp.variables.emplace_back();
|
||||||
kv.key = it.key;
|
auto &kv = resp.variables.back();
|
||||||
|
kv.set_key(StringRef(it.key));
|
||||||
kv.value = it.value.value(x...);
|
kv.value = it.value.value(x...);
|
||||||
resp.variables.push_back(kv);
|
|
||||||
}
|
}
|
||||||
this->parent_->send_homeassistant_service_call(resp);
|
this->parent_->send_homeassistant_service_call(resp);
|
||||||
}
|
}
|
||||||
@ -91,6 +96,6 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
std::vector<TemplatableKeyValuePair<Ts...>> variables_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
@ -6,8 +6,7 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/util.h"
|
#include "esphome/core/util.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
// Generate entity handler implementations using macros
|
// Generate entity handler implementations using macros
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@ -90,6 +89,5 @@ bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
#ifdef USE_API
|
#ifdef USE_API
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class APIConnection;
|
class APIConnection;
|
||||||
|
|
||||||
@ -96,6 +95,5 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
static const char *const TAG = "api.proto";
|
static const char *const TAG = "api.proto";
|
||||||
|
|
||||||
@ -89,5 +88,4 @@ std::string ProtoMessage::dump() const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -3,16 +3,47 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/string_ref.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||||
#define HAS_PROTO_MESSAGE_DUMP
|
#define HAS_PROTO_MESSAGE_DUMP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
/*
|
||||||
|
* StringRef Ownership Model for API Protocol Messages
|
||||||
|
* ===================================================
|
||||||
|
*
|
||||||
|
* StringRef is used for zero-copy string handling in outgoing (SOURCE_SERVER) messages.
|
||||||
|
* It holds a pointer and length to existing string data without copying.
|
||||||
|
*
|
||||||
|
* CRITICAL: The referenced string data MUST remain valid until message encoding completes.
|
||||||
|
*
|
||||||
|
* Safe StringRef Patterns:
|
||||||
|
* 1. String literals: StringRef("literal") - Always safe (static storage duration)
|
||||||
|
* 2. Member variables: StringRef(this->member_string_) - Safe if object outlives encoding
|
||||||
|
* 3. Global/static strings: StringRef(GLOBAL_CONSTANT) - Always safe
|
||||||
|
* 4. Local variables: Safe ONLY if encoding happens before function returns:
|
||||||
|
* std::string temp = compute_value();
|
||||||
|
* msg.set_field(StringRef(temp));
|
||||||
|
* return this->send_message(msg); // temp is valid during encoding
|
||||||
|
*
|
||||||
|
* Unsafe Patterns (WILL cause crashes/corruption):
|
||||||
|
* 1. Temporaries: msg.set_field(StringRef(obj.get_string())) // get_string() returns by value
|
||||||
|
* 2. Concatenation: msg.set_field(StringRef(str1 + str2)) // Result is temporary
|
||||||
|
*
|
||||||
|
* For unsafe patterns, store in a local variable first:
|
||||||
|
* std::string temp = get_string(); // or str1 + str2
|
||||||
|
* msg.set_field(StringRef(temp));
|
||||||
|
*
|
||||||
|
* The send_*_response pattern ensures proper lifetime management by encoding
|
||||||
|
* within the same function scope where temporaries are created.
|
||||||
|
*/
|
||||||
|
|
||||||
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
/// Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit
|
||||||
class ProtoVarInt {
|
class ProtoVarInt {
|
||||||
@ -206,12 +237,20 @@ class ProtoWriteBuffer {
|
|||||||
|
|
||||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
|
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
|
||||||
this->encode_varint_raw(len);
|
this->encode_varint_raw(len);
|
||||||
auto *data = reinterpret_cast<const uint8_t *>(string);
|
|
||||||
this->buffer_->insert(this->buffer_->end(), data, data + len);
|
// Using resize + memcpy instead of insert provides significant performance improvement:
|
||||||
|
// ~10-11x faster for 16-32 byte strings, ~3x faster for 64-byte strings
|
||||||
|
// as it avoids iterator checks and potential element moves that insert performs
|
||||||
|
size_t old_size = this->buffer_->size();
|
||||||
|
this->buffer_->resize(old_size + len);
|
||||||
|
std::memcpy(this->buffer_->data() + old_size, string, len);
|
||||||
}
|
}
|
||||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||||
this->encode_string(field_id, value.data(), value.size(), force);
|
this->encode_string(field_id, value.data(), value.size(), force);
|
||||||
}
|
}
|
||||||
|
void encode_string(uint32_t field_id, const StringRef &ref, bool force = false) {
|
||||||
|
this->encode_string(field_id, ref.c_str(), ref.size(), force);
|
||||||
|
}
|
||||||
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) {
|
||||||
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
this->encode_string(field_id, reinterpret_cast<const char *>(data), len, force);
|
||||||
}
|
}
|
||||||
@ -527,25 +566,6 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + 1;
|
total_size += field_id_size + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Calculates and adds the size of a fixed field to the total message size
|
|
||||||
*
|
|
||||||
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
|
|
||||||
*
|
|
||||||
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
|
|
||||||
* @param is_nonzero Whether the value is non-zero
|
|
||||||
*/
|
|
||||||
template<uint32_t NumBytes>
|
|
||||||
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero) {
|
|
||||||
// Skip calculation if value is zero
|
|
||||||
if (!is_nonzero) {
|
|
||||||
return; // No need to update total_size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixed fields always take exactly NumBytes
|
|
||||||
total_size += field_id_size + NumBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a float field to the total message size
|
* @brief Calculates and adds the size of a float field to the total message size
|
||||||
*/
|
*/
|
||||||
@ -682,17 +702,16 @@ class ProtoSize {
|
|||||||
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
|
// sint64 type is not supported by ESPHome API to reduce overhead on embedded systems
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a string/bytes field to the total message size
|
* @brief Calculates and adds the size of a string field using length
|
||||||
*/
|
*/
|
||||||
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
|
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
|
||||||
// Skip calculation if string is empty
|
// Skip calculation if string is empty
|
||||||
if (str.empty()) {
|
if (len == 0) {
|
||||||
return; // No need to update total_size
|
return; // No need to update total_size
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate and directly add to total_size
|
// Field ID + length varint + string bytes
|
||||||
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
|
||||||
total_size += field_id_size + varint(str_size) + str_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -704,6 +723,19 @@ class ProtoSize {
|
|||||||
total_size += field_id_size + varint(str_size) + str_size;
|
total_size += field_id_size + varint(str_size) + str_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates and adds the size of a bytes field to the total message size
|
||||||
|
*/
|
||||||
|
static inline void add_bytes_field(uint32_t &total_size, uint32_t field_id_size, size_t len) {
|
||||||
|
// Skip calculation if bytes is empty
|
||||||
|
if (len == 0) {
|
||||||
|
return; // No need to update total_size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field ID + length varint + data bytes
|
||||||
|
total_size += field_id_size + varint(static_cast<uint32_t>(len)) + static_cast<uint32_t>(len);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||||
*
|
*
|
||||||
@ -827,7 +859,9 @@ class ProtoService {
|
|||||||
virtual bool is_authenticated() = 0;
|
virtual bool is_authenticated() = 0;
|
||||||
virtual bool is_connection_setup() = 0;
|
virtual bool is_connection_setup() = 0;
|
||||||
virtual void on_fatal_error() = 0;
|
virtual void on_fatal_error() = 0;
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
virtual void on_unauthenticated_access() = 0;
|
virtual void on_unauthenticated_access() = 0;
|
||||||
|
#endif
|
||||||
virtual void on_no_setup_connection() = 0;
|
virtual void on_no_setup_connection() = 0;
|
||||||
/**
|
/**
|
||||||
* Create a buffer with a reserved size.
|
* Create a buffer with a reserved size.
|
||||||
@ -865,6 +899,7 @@ class ProtoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool check_authenticated_() {
|
bool check_authenticated_() {
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
if (!this->check_connection_setup_()) {
|
if (!this->check_connection_setup_()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -873,8 +908,10 @@ class ProtoService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
return this->check_connection_setup_();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
#include "api_connection.h"
|
#include "api_connection.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
// Generate entity handler implementations using macros
|
// Generate entity handler implementations using macros
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
@ -69,6 +68,5 @@ INITIAL_STATE_HANDLER(update, update::UpdateEntity)
|
|||||||
|
|
||||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -5,8 +5,7 @@
|
|||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/component_iterator.h"
|
#include "esphome/core/component_iterator.h"
|
||||||
#include "esphome/core/controller.h"
|
#include "esphome/core/controller.h"
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class APIConnection;
|
class APIConnection;
|
||||||
|
|
||||||
@ -89,6 +88,5 @@ class InitialStateIterator : public ComponentIterator {
|
|||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
#include "user_services.h"
|
#include "user_services.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
|
template<> bool get_execute_arg_value<bool>(const ExecuteServiceArgument &arg) { return arg.bool_; }
|
||||||
template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &arg) {
|
template<> int32_t get_execute_arg_value<int32_t>(const ExecuteServiceArgument &arg) {
|
||||||
@ -40,5 +39,4 @@ template<> enums::ServiceArgType to_service_arg_type<std::vector<std::string>>()
|
|||||||
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
return enums::SERVICE_ARG_TYPE_STRING_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
#include "api_pb2.h"
|
#include "api_pb2.h"
|
||||||
|
|
||||||
#ifdef USE_API_SERVICES
|
#ifdef USE_API_SERVICES
|
||||||
namespace esphome {
|
namespace esphome::api {
|
||||||
namespace api {
|
|
||||||
|
|
||||||
class UserServiceDescriptor {
|
class UserServiceDescriptor {
|
||||||
public:
|
public:
|
||||||
@ -33,14 +32,14 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
|
|||||||
|
|
||||||
ListEntitiesServicesResponse encode_list_service_response() override {
|
ListEntitiesServicesResponse encode_list_service_response() override {
|
||||||
ListEntitiesServicesResponse msg;
|
ListEntitiesServicesResponse msg;
|
||||||
msg.name = this->name_;
|
msg.set_name(StringRef(this->name_));
|
||||||
msg.key = this->key_;
|
msg.key = this->key_;
|
||||||
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
std::array<enums::ServiceArgType, sizeof...(Ts)> arg_types = {to_service_arg_type<Ts>()...};
|
||||||
for (int i = 0; i < sizeof...(Ts); i++) {
|
for (int i = 0; i < sizeof...(Ts); i++) {
|
||||||
ListEntitiesServicesArgument arg;
|
msg.args.emplace_back();
|
||||||
|
auto &arg = msg.args.back();
|
||||||
arg.type = arg_types[i];
|
arg.type = arg_types[i];
|
||||||
arg.name = this->arg_names_[i];
|
arg.set_name(StringRef(this->arg_names_[i]));
|
||||||
msg.args.push_back(arg);
|
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
@ -74,6 +73,5 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
|
|||||||
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
|
void execute(Ts... x) override { this->trigger(x...); } // NOLINT
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace esphome::api
|
||||||
} // namespace esphome
|
|
||||||
#endif // USE_API_SERVICES
|
#endif // USE_API_SERVICES
|
||||||
|
@ -7,8 +7,6 @@ namespace as3935 {
|
|||||||
static const char *const TAG = "as3935";
|
static const char *const TAG = "as3935";
|
||||||
|
|
||||||
void AS3935Component::setup() {
|
void AS3935Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
this->irq_pin_->setup();
|
this->irq_pin_->setup();
|
||||||
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
|
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
|
||||||
|
|
||||||
|
@ -7,9 +7,7 @@ namespace as3935_spi {
|
|||||||
static const char *const TAG = "as3935_spi";
|
static const char *const TAG = "as3935_spi";
|
||||||
|
|
||||||
void SPIAS3935Component::setup() {
|
void SPIAS3935Component::setup() {
|
||||||
ESP_LOGI(TAG, "SPIAS3935Component setup started!");
|
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
ESP_LOGI(TAG, "SPI setup finished!");
|
|
||||||
AS3935Component::setup();
|
AS3935Component::setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,6 @@ static const uint8_t REGISTER_AGC = 0x1A; // 8 bytes / R
|
|||||||
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||||
|
|
||||||
void AS5600Component::setup() {
|
void AS5600Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
if (!this->read_byte(REGISTER_STATUS).has_value()) {
|
if (!this->read_byte(REGISTER_STATUS).has_value()) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -8,7 +8,6 @@ namespace as7341 {
|
|||||||
static const char *const TAG = "as7341";
|
static const char *const TAG = "as7341";
|
||||||
|
|
||||||
void AS7341Component::setup() {
|
void AS7341Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
||||||
// Verify device ID
|
// Verify device ID
|
||||||
|
@ -71,7 +71,7 @@ bool AT581XComponent::i2c_read_reg(uint8_t addr, uint8_t &data) {
|
|||||||
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
|
return this->read_register(addr, &data, 1) == esphome::i2c::NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AT581XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); }
|
void AT581XComponent::setup() {}
|
||||||
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
|
void AT581XComponent::dump_config() { LOG_I2C_DEVICE(this); }
|
||||||
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
|
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
|
||||||
bool AT581XComponent::i2c_write_config() {
|
bool AT581XComponent::i2c_write_config() {
|
||||||
|
@ -41,7 +41,6 @@ void ATM90E26Component::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E26Component::setup() {
|
void ATM90E26Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
|
||||||
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
|
uint16_t mmode = 0x422; // default values for everything but L/N line current gains
|
||||||
|
@ -109,7 +109,6 @@ void ATM90E32Component::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ATM90E32Component::setup() {
|
void ATM90E32Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->spi_setup();
|
this->spi_setup();
|
||||||
|
|
||||||
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
uint16_t mmode0 = 0x87; // 3P4W 50Hz
|
||||||
|
@ -15,7 +15,7 @@ class AudioStreamInfo {
|
|||||||
* - An audio sample represents a unit of audio for one channel.
|
* - An audio sample represents a unit of audio for one channel.
|
||||||
* - A frame represents a unit of audio with a sample for every channel.
|
* - A frame represents a unit of audio with a sample for every channel.
|
||||||
*
|
*
|
||||||
* In gneneral, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames
|
* In general, converting between bytes, samples, and frames shouldn't result in rounding errors so long as frames
|
||||||
* are used as the main unit when transferring audio data. Durations may result in rounding for certain sample rates;
|
* are used as the main unit when transferring audio data. Durations may result in rounding for certain sample rates;
|
||||||
* e.g., 44.1 KHz. The ``frames_to_milliseconds_with_remainder`` function should be used for accuracy, as it takes
|
* e.g., 44.1 KHz. The ``frames_to_milliseconds_with_remainder`` function should be used for accuracy, as it takes
|
||||||
* into account the remainder rather than just ignoring any rounding.
|
* into account the remainder rather than just ignoring any rounding.
|
||||||
@ -76,7 +76,7 @@ class AudioStreamInfo {
|
|||||||
|
|
||||||
/// @brief Computes the duration, in microseconds, the given amount of frames represents.
|
/// @brief Computes the duration, in microseconds, the given amount of frames represents.
|
||||||
/// @param frames Number of audio frames
|
/// @param frames Number of audio frames
|
||||||
/// @return Duration in microseconds `frames` respresents. May be slightly inaccurate due to integer divison rounding
|
/// @return Duration in microseconds `frames` represents. May be slightly inaccurate due to integer division rounding
|
||||||
/// for certain sample rates.
|
/// for certain sample rates.
|
||||||
uint32_t frames_to_microseconds(uint32_t frames) const;
|
uint32_t frames_to_microseconds(uint32_t frames) const;
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ constexpr static const uint8_t AXS_READ_TOUCHPAD[11] = {0xb5, 0xab, 0xa5, 0x5a,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AXS15231Touchscreen::setup() {
|
void AXS15231Touchscreen::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(false);
|
this->reset_pin_->digital_write(false);
|
||||||
@ -36,7 +35,6 @@ void AXS15231Touchscreen::setup() {
|
|||||||
if (this->y_raw_max_ == 0) {
|
if (this->y_raw_max_ == 0) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG, "AXS15231 Touchscreen setup complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AXS15231Touchscreen::update_touches() {
|
void AXS15231Touchscreen::update_touches() {
|
||||||
|
@ -121,8 +121,6 @@ void spi_dma_tx_finish_callback(unsigned int param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BekenSPILEDStripLightOutput::setup() {
|
void BekenSPILEDStripLightOutput::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
size_t buffer_size = this->get_buffer_size_();
|
size_t buffer_size = this->get_buffer_size_();
|
||||||
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
|
size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ MTreg:
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void BH1750Sensor::setup() {
|
void BH1750Sensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->name_.c_str());
|
|
||||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -266,8 +266,10 @@ async def delayed_off_filter_to_code(config, filter_id):
|
|||||||
async def autorepeat_filter_to_code(config, filter_id):
|
async def autorepeat_filter_to_code(config, filter_id):
|
||||||
timings = []
|
timings = []
|
||||||
if len(config) > 0:
|
if len(config) > 0:
|
||||||
for conf in config:
|
timings.extend(
|
||||||
timings.append((conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON]))
|
(conf[CONF_DELAY], conf[CONF_TIME_OFF], conf[CONF_TIME_ON])
|
||||||
|
for conf in config
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
timings.append(
|
timings.append(
|
||||||
(
|
(
|
||||||
@ -573,16 +575,15 @@ async def setup_binary_sensor_core_(var, config):
|
|||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
for conf in config.get(CONF_ON_MULTI_CLICK, []):
|
||||||
timings = []
|
timings = [
|
||||||
for tim in conf[CONF_TIMING]:
|
cg.StructInitializer(
|
||||||
timings.append(
|
MultiClickTriggerEvent,
|
||||||
cg.StructInitializer(
|
("state", tim[CONF_STATE]),
|
||||||
MultiClickTriggerEvent,
|
("min_length", tim[CONF_MIN_LENGTH]),
|
||||||
("state", tim[CONF_STATE]),
|
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
|
||||||
("min_length", tim[CONF_MIN_LENGTH]),
|
|
||||||
("max_length", tim.get(CONF_MAX_LENGTH, 4294967294)),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
for tim in conf[CONF_TIMING]
|
||||||
|
]
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, timings)
|
||||||
if CONF_INVALID_COOLDOWN in conf:
|
if CONF_INVALID_COOLDOWN in conf:
|
||||||
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
cg.add(trigger.set_invalid_cooldown(conf[CONF_INVALID_COOLDOWN]))
|
||||||
|
@ -8,16 +8,184 @@
|
|||||||
|
|
||||||
#include "bluetooth_proxy.h"
|
#include "bluetooth_proxy.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::bluetooth_proxy {
|
||||||
namespace bluetooth_proxy {
|
|
||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy.connection";
|
static const char *const TAG = "bluetooth_proxy.connection";
|
||||||
|
|
||||||
|
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
|
||||||
|
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
||||||
|
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
|
||||||
|
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
||||||
|
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
|
||||||
|
}
|
||||||
|
|
||||||
void BluetoothConnection::dump_config() {
|
void BluetoothConnection::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "BLE Connection:");
|
ESP_LOGCONFIG(TAG, "BLE Connection:");
|
||||||
BLEClientBase::dump_config();
|
BLEClientBase::dump_config();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::loop() {
|
||||||
|
BLEClientBase::loop();
|
||||||
|
|
||||||
|
// Early return if no active connection or not in service discovery phase
|
||||||
|
if (this->address_ == 0 || this->send_service_ < 0 || this->send_service_ > this->service_count_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle service discovery
|
||||||
|
this->send_service_for_discovery_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::reset_connection_(esp_err_t reason) {
|
||||||
|
// Send disconnection notification
|
||||||
|
this->proxy_->send_device_connection(this->address_, false, 0, reason);
|
||||||
|
|
||||||
|
// Important: If we were in the middle of sending services, we do NOT send
|
||||||
|
// send_gatt_services_done() here. This ensures the client knows that
|
||||||
|
// the service discovery was interrupted and can retry. The client
|
||||||
|
// (aioesphomeapi) implements a 30-second timeout (DEFAULT_BLE_TIMEOUT)
|
||||||
|
// to detect incomplete service discovery rather than relying on us to
|
||||||
|
// tell them about a partial list.
|
||||||
|
this->set_address(0);
|
||||||
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
|
this->proxy_->send_connections_free();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BluetoothConnection::send_service_for_discovery_() {
|
||||||
|
if (this->send_service_ == this->service_count_) {
|
||||||
|
this->send_service_ = DONE_SENDING_SERVICES;
|
||||||
|
this->proxy_->send_gatt_services_done(this->address_);
|
||||||
|
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
||||||
|
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||||
|
this->release_services();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early return if no API connection
|
||||||
|
auto *api_conn = this->proxy_->get_api_connection();
|
||||||
|
if (api_conn == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send next service
|
||||||
|
esp_gattc_service_elem_t service_result;
|
||||||
|
uint16_t service_count = 1;
|
||||||
|
esp_gatt_status_t service_status = esp_ble_gattc_get_service(this->gattc_if_, this->conn_id_, nullptr,
|
||||||
|
&service_result, &service_count, this->send_service_);
|
||||||
|
this->send_service_++;
|
||||||
|
|
||||||
|
if (service_status != ESP_GATT_OK || service_count == 0) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service %s, status=%d, service_count=%d, offset=%d",
|
||||||
|
this->connection_index_, this->address_str().c_str(), service_status != ESP_GATT_OK ? "error" : "missing",
|
||||||
|
service_status, service_count, this->send_service_ - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api::BluetoothGATTGetServicesResponse resp;
|
||||||
|
resp.address = this->address_;
|
||||||
|
auto &service_resp = resp.services[0];
|
||||||
|
fill_128bit_uuid_array(service_resp.uuid, service_result.uuid);
|
||||||
|
service_resp.handle = service_result.start_handle;
|
||||||
|
|
||||||
|
// Get the number of characteristics directly with one call
|
||||||
|
uint16_t total_char_count = 0;
|
||||||
|
esp_gatt_status_t char_count_status =
|
||||||
|
esp_ble_gattc_get_attr_count(this->gattc_if_, this->conn_id_, ESP_GATT_DB_CHARACTERISTIC,
|
||||||
|
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
||||||
|
|
||||||
|
if (char_count_status != ESP_GATT_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), char_count_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_char_count == 0) {
|
||||||
|
// No characteristics, just send the service response
|
||||||
|
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve space and process characteristics
|
||||||
|
service_resp.characteristics.reserve(total_char_count);
|
||||||
|
uint16_t char_offset = 0;
|
||||||
|
esp_gattc_char_elem_t char_result;
|
||||||
|
while (true) { // characteristics
|
||||||
|
uint16_t char_count = 1;
|
||||||
|
esp_gatt_status_t char_status =
|
||||||
|
esp_ble_gattc_get_all_char(this->gattc_if_, this->conn_id_, service_result.start_handle,
|
||||||
|
service_result.end_handle, &char_result, &char_count, char_offset);
|
||||||
|
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (char_status != ESP_GATT_OK) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), char_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (char_count == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
service_resp.characteristics.emplace_back();
|
||||||
|
auto &characteristic_resp = service_resp.characteristics.back();
|
||||||
|
fill_128bit_uuid_array(characteristic_resp.uuid, char_result.uuid);
|
||||||
|
characteristic_resp.handle = char_result.char_handle;
|
||||||
|
characteristic_resp.properties = char_result.properties;
|
||||||
|
char_offset++;
|
||||||
|
|
||||||
|
// Get the number of descriptors directly with one call
|
||||||
|
uint16_t total_desc_count = 0;
|
||||||
|
esp_gatt_status_t desc_count_status = esp_ble_gattc_get_attr_count(
|
||||||
|
this->gattc_if_, this->conn_id_, ESP_GATT_DB_DESCRIPTOR, 0, 0, char_result.char_handle, &total_desc_count);
|
||||||
|
|
||||||
|
if (desc_count_status != ESP_GATT_OK) {
|
||||||
|
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), char_result.char_handle, desc_count_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (total_desc_count == 0) {
|
||||||
|
// No descriptors, continue to next characteristic
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve space and process descriptors
|
||||||
|
characteristic_resp.descriptors.reserve(total_desc_count);
|
||||||
|
uint16_t desc_offset = 0;
|
||||||
|
esp_gattc_descr_elem_t desc_result;
|
||||||
|
while (true) { // descriptors
|
||||||
|
uint16_t desc_count = 1;
|
||||||
|
esp_gatt_status_t desc_status = esp_ble_gattc_get_all_descr(
|
||||||
|
this->gattc_if_, this->conn_id_, char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
||||||
|
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (desc_status != ESP_GATT_OK) {
|
||||||
|
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", this->connection_index_,
|
||||||
|
this->address_str().c_str(), desc_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (desc_count == 0) {
|
||||||
|
break; // No more descriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
characteristic_resp.descriptors.emplace_back();
|
||||||
|
auto &descriptor_resp = characteristic_resp.descriptors.back();
|
||||||
|
fill_128bit_uuid_array(descriptor_resp.uuid, desc_result.uuid);
|
||||||
|
descriptor_resp.handle = desc_result.handle;
|
||||||
|
desc_offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message (we already checked api_conn is not null at the beginning)
|
||||||
|
api_conn->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
if (!BLEClientBase::gattc_event_handler(event, gattc_if, param))
|
||||||
@ -25,22 +193,16 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
|
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case ESP_GATTC_DISCONNECT_EVT: {
|
case ESP_GATTC_DISCONNECT_EVT: {
|
||||||
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
|
this->reset_connection_(param->disconnect.reason);
|
||||||
this->set_address(0);
|
|
||||||
this->proxy_->send_connections_free();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_CLOSE_EVT: {
|
case ESP_GATTC_CLOSE_EVT: {
|
||||||
this->proxy_->send_device_connection(this->address_, false, 0, param->close.reason);
|
this->reset_connection_(param->close.reason);
|
||||||
this->set_address(0);
|
|
||||||
this->proxy_->send_connections_free();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESP_GATTC_OPEN_EVT: {
|
case ESP_GATTC_OPEN_EVT: {
|
||||||
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
|
||||||
this->proxy_->send_device_connection(this->address_, false, 0, param->open.status);
|
this->reset_connection_(param->open.status);
|
||||||
this->set_address(0);
|
|
||||||
this->proxy_->send_connections_free();
|
|
||||||
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
|
||||||
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
this->proxy_->send_device_connection(this->address_, true, this->mtu_);
|
||||||
this->proxy_->send_connections_free();
|
this->proxy_->send_connections_free();
|
||||||
@ -72,9 +234,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
api::BluetoothGATTReadResponse resp;
|
api::BluetoothGATTReadResponse resp;
|
||||||
resp.address = this->address_;
|
resp.address = this->address_;
|
||||||
resp.handle = param->read.handle;
|
resp.handle = param->read.handle;
|
||||||
resp.data.reserve(param->read.value_len);
|
resp.set_data(param->read.value, param->read.value_len);
|
||||||
// Use bulk insert instead of individual push_backs
|
|
||||||
resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
|
|
||||||
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTReadResponse::MESSAGE_TYPE);
|
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTReadResponse::MESSAGE_TYPE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -125,9 +285,7 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
|
|||||||
api::BluetoothGATTNotifyDataResponse resp;
|
api::BluetoothGATTNotifyDataResponse resp;
|
||||||
resp.address = this->address_;
|
resp.address = this->address_;
|
||||||
resp.handle = param->notify.handle;
|
resp.handle = param->notify.handle;
|
||||||
resp.data.reserve(param->notify.value_len);
|
resp.set_data(param->notify.value, param->notify.value_len);
|
||||||
// Use bulk insert instead of individual push_backs
|
|
||||||
resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
|
|
||||||
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyDataResponse::MESSAGE_TYPE);
|
this->proxy_->get_api_connection()->send_message(resp, api::BluetoothGATTNotifyDataResponse::MESSAGE_TYPE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -265,7 +423,6 @@ esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisemen
|
|||||||
return this->proxy_->get_advertisement_parser_type();
|
return this->proxy_->get_advertisement_parser_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace bluetooth_proxy
|
} // namespace esphome::bluetooth_proxy
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@ -4,14 +4,14 @@
|
|||||||
|
|
||||||
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
#include "esphome/components/esp32_ble_client/ble_client_base.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::bluetooth_proxy {
|
||||||
namespace bluetooth_proxy {
|
|
||||||
|
|
||||||
class BluetoothProxy;
|
class BluetoothProxy;
|
||||||
|
|
||||||
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
void loop() override;
|
||||||
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) override;
|
esp_ble_gattc_cb_param_t *param) override;
|
||||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||||
@ -27,6 +27,9 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
|||||||
protected:
|
protected:
|
||||||
friend class BluetoothProxy;
|
friend class BluetoothProxy;
|
||||||
|
|
||||||
|
void send_service_for_discovery_();
|
||||||
|
void reset_connection_(esp_err_t reason);
|
||||||
|
|
||||||
// Memory optimized layout for 32-bit systems
|
// Memory optimized layout for 32-bit systems
|
||||||
// Group 1: Pointers (4 bytes each, naturally aligned)
|
// Group 1: Pointers (4 bytes each, naturally aligned)
|
||||||
BluetoothProxy *proxy_;
|
BluetoothProxy *proxy_;
|
||||||
@ -39,7 +42,6 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase {
|
|||||||
// 1 byte used, 1 byte padding
|
// 1 byte used, 1 byte padding
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bluetooth_proxy
|
} // namespace esphome::bluetooth_proxy
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@ -3,30 +3,38 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/macros.h"
|
#include "esphome/core/macros.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::bluetooth_proxy {
|
||||||
namespace bluetooth_proxy {
|
|
||||||
|
|
||||||
static const char *const TAG = "bluetooth_proxy";
|
static const char *const TAG = "bluetooth_proxy";
|
||||||
static const int DONE_SENDING_SERVICES = -2;
|
|
||||||
|
|
||||||
std::vector<uint64_t> get_128bit_uuid_vec(esp_bt_uuid_t uuid_source) {
|
// Batch size for BLE advertisements to maximize WiFi efficiency
|
||||||
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
|
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
||||||
return std::vector<uint64_t>{((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
|
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
|
||||||
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
|
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
||||||
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
|
// This achieves ~97% WiFi MTU utilization while staying under the limit
|
||||||
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]),
|
static constexpr size_t FLUSH_BATCH_SIZE = 16;
|
||||||
((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
|
|
||||||
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
|
// Verify BLE advertisement data array size matches the BLE specification (31 bytes adv + 31 bytes scan response)
|
||||||
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
|
static_assert(sizeof(((api::BluetoothLERawAdvertisement *) nullptr)->data) == 62,
|
||||||
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0])};
|
"BLE advertisement data array size mismatch");
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
BluetoothProxy::BluetoothProxy() { global_bluetooth_proxy = this; }
|
||||||
|
|
||||||
void BluetoothProxy::setup() {
|
void BluetoothProxy::setup() {
|
||||||
|
// Pre-allocate response object
|
||||||
|
this->response_ = std::make_unique<api::BluetoothLERawAdvertisementsResponse>();
|
||||||
|
|
||||||
|
// Reserve capacity but start with size 0
|
||||||
|
// Reserve 50% since we'll grow naturally and flush at FLUSH_BATCH_SIZE
|
||||||
|
this->response_->advertisements.reserve(FLUSH_BATCH_SIZE / 2);
|
||||||
|
|
||||||
|
// Don't pre-allocate pool - let it grow only if needed in busy environments
|
||||||
|
// Many devices in quiet areas will never need the overflow pool
|
||||||
|
|
||||||
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
|
||||||
if (this->api_connection_ != nullptr) {
|
if (this->api_connection_ != nullptr) {
|
||||||
this->send_bluetooth_scanner_state_(state);
|
this->send_bluetooth_scanner_state_(state);
|
||||||
@ -50,110 +58,74 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Batch size for BLE advertisements to maximize WiFi efficiency
|
|
||||||
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
|
|
||||||
// Most advertisements are 20-30 bytes, allowing even more to fit per packet
|
|
||||||
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload
|
|
||||||
// This achieves ~97% WiFi MTU utilization while staying under the limit
|
|
||||||
static constexpr size_t FLUSH_BATCH_SIZE = 16;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Batch buffer in anonymous namespace to avoid guard variable (saves 8 bytes)
|
|
||||||
// This is initialized at program startup before any threads
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
|
||||||
std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { return batch_buffer; }
|
|
||||||
|
|
||||||
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
|
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
|
||||||
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Get the batch buffer reference
|
auto &advertisements = this->response_->advertisements;
|
||||||
auto &batch_buffer = get_batch_buffer();
|
|
||||||
|
|
||||||
// Reserve additional capacity if needed
|
|
||||||
size_t new_size = batch_buffer.size() + count;
|
|
||||||
if (batch_buffer.capacity() < new_size) {
|
|
||||||
batch_buffer.reserve(new_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new advertisements to the batch buffer
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
auto &result = scan_results[i];
|
auto &result = scan_results[i];
|
||||||
uint8_t length = result.adv_data_len + result.scan_rsp_len;
|
uint8_t length = result.adv_data_len + result.scan_rsp_len;
|
||||||
|
|
||||||
batch_buffer.emplace_back();
|
// Check if we need to expand the vector
|
||||||
auto &adv = batch_buffer.back();
|
if (this->advertisement_count_ >= advertisements.size()) {
|
||||||
|
if (this->advertisement_pool_.empty()) {
|
||||||
|
// No room in pool, need to allocate
|
||||||
|
advertisements.emplace_back();
|
||||||
|
} else {
|
||||||
|
// Pull from pool
|
||||||
|
advertisements.push_back(std::move(this->advertisement_pool_.back()));
|
||||||
|
this->advertisement_pool_.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in the data directly at current position
|
||||||
|
auto &adv = advertisements[this->advertisement_count_];
|
||||||
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
|
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
|
||||||
adv.rssi = result.rssi;
|
adv.rssi = result.rssi;
|
||||||
adv.address_type = result.ble_addr_type;
|
adv.address_type = result.ble_addr_type;
|
||||||
adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]);
|
adv.data_len = length;
|
||||||
|
std::memcpy(adv.data, result.ble_adv, length);
|
||||||
|
|
||||||
|
this->advertisement_count_++;
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
|
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
|
||||||
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
|
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
|
||||||
}
|
|
||||||
|
|
||||||
// Only send if we've accumulated a good batch size to maximize batching efficiency
|
// Flush if we have reached FLUSH_BATCH_SIZE
|
||||||
// https://github.com/esphome/backlog/issues/21
|
if (this->advertisement_count_ >= FLUSH_BATCH_SIZE) {
|
||||||
if (batch_buffer.size() >= FLUSH_BATCH_SIZE) {
|
this->flush_pending_advertisements();
|
||||||
this->flush_pending_advertisements();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BluetoothProxy::flush_pending_advertisements() {
|
void BluetoothProxy::flush_pending_advertisements() {
|
||||||
auto &batch_buffer = get_batch_buffer();
|
if (this->advertisement_count_ == 0 || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
||||||
if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
api::BluetoothLERawAdvertisementsResponse resp;
|
auto &advertisements = this->response_->advertisements;
|
||||||
resp.advertisements.swap(batch_buffer);
|
|
||||||
this->api_connection_->send_message(resp, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
// Return any items beyond advertisement_count_ to the pool
|
||||||
|
if (advertisements.size() > this->advertisement_count_) {
|
||||||
|
// Move unused items back to pool
|
||||||
|
this->advertisement_pool_.insert(this->advertisement_pool_.end(),
|
||||||
|
std::make_move_iterator(advertisements.begin() + this->advertisement_count_),
|
||||||
|
std::make_move_iterator(advertisements.end()));
|
||||||
|
|
||||||
|
// Resize to actual count
|
||||||
|
advertisements.resize(this->advertisement_count_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the message
|
||||||
|
this->api_connection_->send_message(*this->response_, api::BluetoothLERawAdvertisementsResponse::MESSAGE_TYPE);
|
||||||
|
|
||||||
|
// Reset count - existing items will be overwritten in next batch
|
||||||
|
this->advertisement_count_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
|
|
||||||
api::BluetoothLEAdvertisementResponse resp;
|
|
||||||
resp.address = device.address_uint64();
|
|
||||||
resp.address_type = device.get_address_type();
|
|
||||||
if (!device.get_name().empty())
|
|
||||||
resp.name = device.get_name();
|
|
||||||
resp.rssi = device.get_rssi();
|
|
||||||
|
|
||||||
// Pre-allocate vectors based on known sizes
|
|
||||||
auto service_uuids = device.get_service_uuids();
|
|
||||||
resp.service_uuids.reserve(service_uuids.size());
|
|
||||||
for (auto &uuid : service_uuids) {
|
|
||||||
resp.service_uuids.emplace_back(uuid.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate service data vector
|
|
||||||
auto service_datas = device.get_service_datas();
|
|
||||||
resp.service_data.reserve(service_datas.size());
|
|
||||||
for (auto &data : service_datas) {
|
|
||||||
resp.service_data.emplace_back();
|
|
||||||
auto &service_data = resp.service_data.back();
|
|
||||||
service_data.uuid = data.uuid.to_string();
|
|
||||||
service_data.data.assign(data.data.begin(), data.data.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-allocate manufacturer data vector
|
|
||||||
auto manufacturer_datas = device.get_manufacturer_datas();
|
|
||||||
resp.manufacturer_data.reserve(manufacturer_datas.size());
|
|
||||||
for (auto &data : manufacturer_datas) {
|
|
||||||
resp.manufacturer_data.emplace_back();
|
|
||||||
auto &manufacturer_data = resp.manufacturer_data.back();
|
|
||||||
manufacturer_data.uuid = data.uuid.to_string();
|
|
||||||
manufacturer_data.data.assign(data.data.begin(), data.data.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
this->api_connection_->send_message(resp, api::BluetoothLEAdvertisementResponse::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
#endif // USE_ESP32_BLE_DEVICE
|
|
||||||
|
|
||||||
void BluetoothProxy::dump_config() {
|
void BluetoothProxy::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
@ -187,130 +159,12 @@ void BluetoothProxy::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
// Flush any pending BLE advertisements that have been accumulated but not yet sent
|
||||||
static uint32_t last_flush_time = 0;
|
|
||||||
uint32_t now = App.get_loop_component_start_time();
|
uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Flush accumulated advertisements every 100ms
|
// Flush accumulated advertisements every 100ms
|
||||||
if (now - last_flush_time >= 100) {
|
if (now - this->last_advertisement_flush_time_ >= 100) {
|
||||||
this->flush_pending_advertisements();
|
this->flush_pending_advertisements();
|
||||||
last_flush_time = now;
|
this->last_advertisement_flush_time_ = now;
|
||||||
}
|
|
||||||
for (auto *connection : this->connections_) {
|
|
||||||
if (connection->send_service_ == connection->service_count_) {
|
|
||||||
connection->send_service_ = DONE_SENDING_SERVICES;
|
|
||||||
this->send_gatt_services_done(connection->get_address());
|
|
||||||
if (connection->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
|
|
||||||
connection->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
|
||||||
connection->release_services();
|
|
||||||
}
|
|
||||||
} else if (connection->send_service_ >= 0) {
|
|
||||||
esp_gattc_service_elem_t service_result;
|
|
||||||
uint16_t service_count = 1;
|
|
||||||
esp_gatt_status_t service_status =
|
|
||||||
esp_ble_gattc_get_service(connection->get_gattc_if(), connection->get_conn_id(), nullptr, &service_result,
|
|
||||||
&service_count, connection->send_service_);
|
|
||||||
connection->send_service_++;
|
|
||||||
if (service_status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service error at offset=%d, status=%d",
|
|
||||||
connection->get_connection_index(), connection->address_str().c_str(), connection->send_service_ - 1,
|
|
||||||
service_status);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (service_count == 0) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_service missing, service_count=%d",
|
|
||||||
connection->get_connection_index(), connection->address_str().c_str(), service_count);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
api::BluetoothGATTGetServicesResponse resp;
|
|
||||||
resp.address = connection->get_address();
|
|
||||||
resp.services.reserve(1); // Always one service per response in this implementation
|
|
||||||
api::BluetoothGATTService service_resp;
|
|
||||||
service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
|
|
||||||
service_resp.handle = service_result.start_handle;
|
|
||||||
uint16_t char_offset = 0;
|
|
||||||
esp_gattc_char_elem_t char_result;
|
|
||||||
// Get the number of characteristics directly with one call
|
|
||||||
uint16_t total_char_count = 0;
|
|
||||||
esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
|
|
||||||
connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
|
|
||||||
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
|
|
||||||
|
|
||||||
if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
|
|
||||||
// Only reserve if we successfully got a count
|
|
||||||
service_resp.characteristics.reserve(total_char_count);
|
|
||||||
} else if (char_count_status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
|
|
||||||
connection->address_str().c_str(), char_count_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now process characteristics
|
|
||||||
while (true) { // characteristics
|
|
||||||
uint16_t char_count = 1;
|
|
||||||
esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
|
|
||||||
connection->get_gattc_if(), connection->get_conn_id(), service_result.start_handle,
|
|
||||||
service_result.end_handle, &char_result, &char_count, char_offset);
|
|
||||||
if (char_status == ESP_GATT_INVALID_OFFSET || char_status == ESP_GATT_NOT_FOUND) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (char_status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_char error, status=%d", connection->get_connection_index(),
|
|
||||||
connection->address_str().c_str(), char_status);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (char_count == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
api::BluetoothGATTCharacteristic characteristic_resp;
|
|
||||||
characteristic_resp.uuid = get_128bit_uuid_vec(char_result.uuid);
|
|
||||||
characteristic_resp.handle = char_result.char_handle;
|
|
||||||
characteristic_resp.properties = char_result.properties;
|
|
||||||
char_offset++;
|
|
||||||
|
|
||||||
// Get the number of descriptors directly with one call
|
|
||||||
uint16_t total_desc_count = 0;
|
|
||||||
esp_gatt_status_t desc_count_status =
|
|
||||||
esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
|
|
||||||
char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
|
|
||||||
|
|
||||||
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
|
|
||||||
// Only reserve if we successfully got a count
|
|
||||||
characteristic_resp.descriptors.reserve(total_desc_count);
|
|
||||||
} else if (desc_count_status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
|
|
||||||
connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
|
|
||||||
desc_count_status);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now process descriptors
|
|
||||||
uint16_t desc_offset = 0;
|
|
||||||
esp_gattc_descr_elem_t desc_result;
|
|
||||||
while (true) { // descriptors
|
|
||||||
uint16_t desc_count = 1;
|
|
||||||
esp_gatt_status_t desc_status =
|
|
||||||
esp_ble_gattc_get_all_descr(connection->get_gattc_if(), connection->get_conn_id(),
|
|
||||||
char_result.char_handle, &desc_result, &desc_count, desc_offset);
|
|
||||||
if (desc_status == ESP_GATT_INVALID_OFFSET || desc_status == ESP_GATT_NOT_FOUND) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (desc_status != ESP_GATT_OK) {
|
|
||||||
ESP_LOGE(TAG, "[%d] [%s] esp_ble_gattc_get_all_descr error, status=%d", connection->get_connection_index(),
|
|
||||||
connection->address_str().c_str(), desc_status);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (desc_count == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
api::BluetoothGATTDescriptor descriptor_resp;
|
|
||||||
descriptor_resp.uuid = get_128bit_uuid_vec(desc_result.uuid);
|
|
||||||
descriptor_resp.handle = desc_result.handle;
|
|
||||||
characteristic_resp.descriptors.push_back(std::move(descriptor_resp));
|
|
||||||
desc_offset++;
|
|
||||||
}
|
|
||||||
service_resp.characteristics.push_back(std::move(characteristic_resp));
|
|
||||||
}
|
|
||||||
resp.services.push_back(std::move(service_resp));
|
|
||||||
this->api_connection_->send_message(resp, api::BluetoothGATTGetServicesResponse::MESSAGE_TYPE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,7 +501,6 @@ void BluetoothProxy::bluetooth_scanner_set_mode(bool active) {
|
|||||||
|
|
||||||
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
BluetoothProxy *global_bluetooth_proxy = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
} // namespace bluetooth_proxy
|
} // namespace esphome::bluetooth_proxy
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
#include <esp_bt.h>
|
#include <esp_bt.h>
|
||||||
#include <esp_bt_device.h>
|
#include <esp_bt_device.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::bluetooth_proxy {
|
||||||
namespace bluetooth_proxy {
|
|
||||||
|
|
||||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||||
|
static const int DONE_SENDING_SERVICES = -2;
|
||||||
|
|
||||||
using namespace esp32_ble_client;
|
using namespace esp32_ble_client;
|
||||||
|
|
||||||
@ -131,9 +131,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
#ifdef USE_ESP32_BLE_DEVICE
|
|
||||||
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
|
|
||||||
#endif
|
|
||||||
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
|
||||||
|
|
||||||
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
|
||||||
@ -145,14 +142,21 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
|
|||||||
// Group 2: Container types (typically 12 bytes on 32-bit)
|
// Group 2: Container types (typically 12 bytes on 32-bit)
|
||||||
std::vector<BluetoothConnection *> connections_{};
|
std::vector<BluetoothConnection *> connections_{};
|
||||||
|
|
||||||
// Group 3: 1-byte types grouped together
|
// BLE advertisement batching
|
||||||
|
std::vector<api::BluetoothLERawAdvertisement> advertisement_pool_;
|
||||||
|
std::unique_ptr<api::BluetoothLERawAdvertisementsResponse> response_;
|
||||||
|
|
||||||
|
// Group 3: 4-byte types
|
||||||
|
uint32_t last_advertisement_flush_time_{0};
|
||||||
|
|
||||||
|
// Group 4: 1-byte types grouped together
|
||||||
bool active_;
|
bool active_;
|
||||||
// 1 byte used, 3 bytes padding
|
uint8_t advertisement_count_{0};
|
||||||
|
// 2 bytes used, 2 bytes padding
|
||||||
};
|
};
|
||||||
|
|
||||||
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
} // namespace bluetooth_proxy
|
} // namespace esphome::bluetooth_proxy
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
@ -88,7 +88,6 @@ const char *oversampling_to_str(BME280Oversampling oversampling) { // NOLINT
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BME280Component::setup() {
|
void BME280Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t chip_id = 0;
|
uint8_t chip_id = 0;
|
||||||
|
|
||||||
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
// Mark as not failed before initializing. Some devices will turn off sensors to save on batteries
|
||||||
|
@ -71,7 +71,6 @@ static const char *iir_filter_to_str(BME680IIRFilter filter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BME680Component::setup() {
|
void BME680Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t chip_id;
|
uint8_t chip_id;
|
||||||
if (!this->read_byte(BME680_REGISTER_CHIPID, &chip_id) || chip_id != 0x61) {
|
if (!this->read_byte(BME680_REGISTER_CHIPID, &chip_id) || chip_id != 0x61) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import esp32, i2c
|
from esphome.components import esp32, i2c
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
|
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET, Framework
|
||||||
|
|
||||||
CODEOWNERS = ["@trvrnrth"]
|
CODEOWNERS = ["@trvrnrth"]
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
@ -56,7 +56,15 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
): cv.positive_time_period_minutes,
|
): cv.positive_time_period_minutes,
|
||||||
}
|
}
|
||||||
).extend(i2c.i2c_device_schema(0x76)),
|
).extend(i2c.i2c_device_schema(0x76)),
|
||||||
cv.only_with_arduino,
|
cv.only_with_framework(
|
||||||
|
frameworks=Framework.ARDUINO,
|
||||||
|
suggestions={
|
||||||
|
Framework.ESP_IDF: (
|
||||||
|
"bme68x_bsec2_i2c",
|
||||||
|
"sensor/bme68x_bsec2",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
cv.Any(
|
cv.Any(
|
||||||
cv.only_on_esp8266,
|
cv.only_on_esp8266,
|
||||||
cv.All(
|
cv.All(
|
||||||
|
@ -15,8 +15,6 @@ std::vector<BME680BSECComponent *>
|
|||||||
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
|
uint8_t BME680BSECComponent::work_buffer_[BSEC_MAX_WORKBUFFER_SIZE] = {0};
|
||||||
|
|
||||||
void BME680BSECComponent::setup() {
|
void BME680BSECComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup for '%s'", this->device_id_.c_str());
|
|
||||||
|
|
||||||
uint8_t new_idx = BME680BSECComponent::instances.size();
|
uint8_t new_idx = BME680BSECComponent::instances.size();
|
||||||
BME680BSECComponent::instances.push_back(this);
|
BME680BSECComponent::instances.push_back(this);
|
||||||
|
|
||||||
|
@ -21,8 +21,6 @@ static const char *const TAG = "bme68x_bsec2.sensor";
|
|||||||
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||||
|
|
||||||
void BME68xBSEC2Component::setup() {
|
void BME68xBSEC2Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
|
this->bsec_status_ = bsec_init_m(&this->bsec_instance_);
|
||||||
if (this->bsec_status_ != BSEC_OK) {
|
if (this->bsec_status_ != BSEC_OK) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -119,7 +119,6 @@ const float GRAVITY_EARTH = 9.80665f;
|
|||||||
void BMI160Component::internal_setup_(int stage) {
|
void BMI160Component::internal_setup_(int stage) {
|
||||||
switch (stage) {
|
switch (stage) {
|
||||||
case 0:
|
case 0:
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t chipid;
|
uint8_t chipid;
|
||||||
if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
|
if (!this->read_byte(BMI160_REGISTER_CHIPID, &chipid) || (chipid != 0b11010001)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -20,7 +20,6 @@ void BMP085Component::update() {
|
|||||||
this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); });
|
this->set_timeout("temperature", 5, [this]() { this->read_temperature_(); });
|
||||||
}
|
}
|
||||||
void BMP085Component::setup() {
|
void BMP085Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t data[22];
|
uint8_t data[22];
|
||||||
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
|
if (!this->read_bytes(BMP085_REGISTER_AC1_H, data, 22)) {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -57,7 +57,6 @@ static const char *iir_filter_to_str(BMP280IIRFilter filter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BMP280Component::setup() {
|
void BMP280Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t chip_id = 0;
|
uint8_t chip_id = 0;
|
||||||
|
|
||||||
// Read the chip id twice, to work around a bug where the first read is 0.
|
// Read the chip id twice, to work around a bug where the first read is 0.
|
||||||
|
@ -70,7 +70,6 @@ static const LogString *iir_filter_to_str(IIRFilter filter) {
|
|||||||
|
|
||||||
void BMP3XXComponent::setup() {
|
void BMP3XXComponent::setup() {
|
||||||
this->error_code_ = NONE;
|
this->error_code_ = NONE;
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
// Call the Device base class "initialise" function
|
// Call the Device base class "initialise" function
|
||||||
if (!reset()) {
|
if (!reset()) {
|
||||||
ESP_LOGE(TAG, "Failed to reset");
|
ESP_LOGE(TAG, "Failed to reset");
|
||||||
|
@ -128,8 +128,6 @@ void BMP581Component::setup() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
this->error_code_ = NONE;
|
this->error_code_ = NONE;
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// 1) Soft reboot //
|
// 1) Soft reboot //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
@ -15,7 +15,6 @@ static const uint8_t BP1658CJ_ADDR_START_5CH = 0x30;
|
|||||||
static const uint8_t BP1658CJ_DELAY = 2;
|
static const uint8_t BP1658CJ_DELAY = 2;
|
||||||
|
|
||||||
void BP1658CJ::setup() {
|
void BP1658CJ::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->data_pin_->setup();
|
this->data_pin_->setup();
|
||||||
this->data_pin_->digital_write(false);
|
this->data_pin_->digital_write(false);
|
||||||
this->clock_pin_->setup();
|
this->clock_pin_->setup();
|
||||||
|
@ -20,7 +20,6 @@ static const uint8_t BP5758D_ALL_DATA_CHANNEL_ENABLEMENT = 0b00011111;
|
|||||||
static const uint8_t BP5758D_DELAY = 2;
|
static const uint8_t BP5758D_DELAY = 2;
|
||||||
|
|
||||||
void BP5758D::setup() {
|
void BP5758D::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->data_pin_->setup();
|
this->data_pin_->setup();
|
||||||
this->data_pin_->digital_write(false);
|
this->data_pin_->digital_write(false);
|
||||||
delayMicroseconds(BP5758D_DELAY);
|
delayMicroseconds(BP5758D_DELAY);
|
||||||
|
@ -22,9 +22,8 @@ def validate_id(config):
|
|||||||
if CONF_CAN_ID in config:
|
if CONF_CAN_ID in config:
|
||||||
can_id = config[CONF_CAN_ID]
|
can_id = config[CONF_CAN_ID]
|
||||||
id_ext = config[CONF_USE_EXTENDED_ID]
|
id_ext = config[CONF_USE_EXTENDED_ID]
|
||||||
if not id_ext:
|
if not id_ext and can_id > 0x7FF:
|
||||||
if can_id > 0x7FF:
|
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ namespace canbus {
|
|||||||
static const char *const TAG = "canbus";
|
static const char *const TAG = "canbus";
|
||||||
|
|
||||||
void Canbus::setup() {
|
void Canbus::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (!this->setup_internal()) {
|
if (!this->setup_internal()) {
|
||||||
ESP_LOGE(TAG, "setup error!");
|
ESP_LOGE(TAG, "setup error!");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -8,8 +8,6 @@ namespace cap1188 {
|
|||||||
static const char *const TAG = "cap1188";
|
static const char *const TAG = "cap1188";
|
||||||
|
|
||||||
void CAP1188Component::setup() {
|
void CAP1188Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
// Reset device using the reset pin
|
// Reset device using the reset pin
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
|
@ -10,8 +10,6 @@ static const char *const TAG = "cd74hc4067";
|
|||||||
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
|
float CD74HC4067Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
void CD74HC4067Component::setup() {
|
void CD74HC4067Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
this->pin_s0_->setup();
|
this->pin_s0_->setup();
|
||||||
this->pin_s1_->setup();
|
this->pin_s1_->setup();
|
||||||
this->pin_s2_->setup();
|
this->pin_s2_->setup();
|
||||||
|
@ -14,7 +14,6 @@ static const uint8_t CH422G_REG_OUT_UPPER = 0x23; // write reg for output bit
|
|||||||
static const char *const TAG = "ch422g";
|
static const char *const TAG = "ch422g";
|
||||||
|
|
||||||
void CH422GComponent::setup() {
|
void CH422GComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
// set outputs before mode
|
// set outputs before mode
|
||||||
this->write_outputs_();
|
this->write_outputs_();
|
||||||
// Set mode and check for errors
|
// Set mode and check for errors
|
||||||
|
@ -4,7 +4,6 @@ namespace esphome {
|
|||||||
namespace chsc6x {
|
namespace chsc6x {
|
||||||
|
|
||||||
void CHSC6XTouchscreen::setup() {
|
void CHSC6XTouchscreen::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (this->interrupt_pin_ != nullptr) {
|
if (this->interrupt_pin_ != nullptr) {
|
||||||
this->interrupt_pin_->setup();
|
this->interrupt_pin_->setup();
|
||||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||||
@ -15,8 +14,6 @@ void CHSC6XTouchscreen::setup() {
|
|||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, "CHSC6X Touchscreen setup complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHSC6XTouchscreen::update_touches() {
|
void CHSC6XTouchscreen::update_touches() {
|
||||||
|
@ -20,7 +20,6 @@ uint8_t cm1106_checksum(const uint8_t *response, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CM1106Component::setup() {
|
void CM1106Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
uint8_t response[8] = {0};
|
uint8_t response[8] = {0};
|
||||||
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
|
if (!this->cm1106_write_command_(C_M1106_CMD_GET_CO2, sizeof(C_M1106_CMD_GET_CO2), response, sizeof(response))) {
|
||||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||||
|
@ -3,7 +3,12 @@
|
|||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
CONF_BYTE_ORDER = "byte_order"
|
CONF_BYTE_ORDER = "byte_order"
|
||||||
|
BYTE_ORDER_LITTLE = "little_endian"
|
||||||
|
BYTE_ORDER_BIG = "big_endian"
|
||||||
|
|
||||||
CONF_COLOR_DEPTH = "color_depth"
|
CONF_COLOR_DEPTH = "color_depth"
|
||||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||||
|
CONF_ON_RECEIVE = "on_receive"
|
||||||
CONF_ON_STATE_CHANGE = "on_state_change"
|
CONF_ON_STATE_CHANGE = "on_state_change"
|
||||||
CONF_REQUEST_HEADERS = "request_headers"
|
CONF_REQUEST_HEADERS = "request_headers"
|
||||||
|
CONF_USE_PSRAM = "use_psram"
|
||||||
|
@ -52,8 +52,6 @@ bool CS5460AComponent::softreset_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CS5460AComponent::setup() {
|
void CS5460AComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
|
float current_full_scale = (pga_gain_ == CS5460A_PGA_GAIN_10X) ? 0.25 : 0.10;
|
||||||
float voltage_full_scale = 0.25;
|
float voltage_full_scale = 0.25;
|
||||||
current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
|
current_multiplier_ = current_full_scale / (fabsf(current_gain_) * 0x1000000);
|
||||||
|
@ -42,7 +42,6 @@ static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
|
|||||||
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
|
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
|
||||||
|
|
||||||
void CSE7761Component::setup() {
|
void CSE7761Component::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
|
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
|
||||||
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
|
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
|
||||||
if ((0x0A04 == syscon) && this->chip_init_()) {
|
if ((0x0A04 == syscon) && this->chip_init_()) {
|
||||||
|
@ -6,7 +6,6 @@ namespace cst226 {
|
|||||||
static const char *const TAG = "cst226.touchscreen";
|
static const char *const TAG = "cst226.touchscreen";
|
||||||
|
|
||||||
void CST226Touchscreen::setup() {
|
void CST226Touchscreen::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
@ -95,7 +94,6 @@ void CST226Touchscreen::continue_setup_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->setup_complete_ = true;
|
this->setup_complete_ = true;
|
||||||
ESP_LOGCONFIG(TAG, "CST226 Touchscreen setup complete");
|
|
||||||
}
|
}
|
||||||
void CST226Touchscreen::update_button_state_(bool state) {
|
void CST226Touchscreen::update_button_state_(bool state) {
|
||||||
if (this->button_touched_ == state)
|
if (this->button_touched_ == state)
|
||||||
|
@ -35,11 +35,9 @@ void CST816Touchscreen::continue_setup_() {
|
|||||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||||
this->y_raw_max_ = this->display_->get_native_height();
|
this->y_raw_max_ = this->display_->get_native_height();
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG, "CST816 Touchscreen setup complete");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CST816Touchscreen::setup() {
|
void CST816Touchscreen::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (this->reset_pin_ != nullptr) {
|
if (this->reset_pin_ != nullptr) {
|
||||||
this->reset_pin_->setup();
|
this->reset_pin_->setup();
|
||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
|
@ -20,8 +20,6 @@ static const uint8_t DAC7678_REG_INTERNAL_REF_0 = 0x80;
|
|||||||
static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90;
|
static const uint8_t DAC7678_REG_INTERNAL_REF_1 = 0x90;
|
||||||
|
|
||||||
void DAC7678Output::setup() {
|
void DAC7678Output::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Resetting device");
|
ESP_LOGV(TAG, "Resetting device");
|
||||||
|
|
||||||
// Reset device
|
// Reset device
|
||||||
|
@ -70,7 +70,6 @@ bool DallasTemperatureSensor::read_scratch_pad_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DallasTemperatureSensor::setup() {
|
void DallasTemperatureSensor::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
if (!this->check_address_())
|
if (!this->check_address_())
|
||||||
return;
|
return;
|
||||||
if (!this->read_scratch_pad_())
|
if (!this->read_scratch_pad_())
|
||||||
|
@ -12,7 +12,6 @@ static const uint32_t TEARDOWN_TIMEOUT_DEEP_SLEEP_MS = 5000;
|
|||||||
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
bool global_has_deep_sleep = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
void DeepSleepComponent::setup() {
|
void DeepSleepComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
global_has_deep_sleep = true;
|
global_has_deep_sleep = true;
|
||||||
|
|
||||||
const optional<uint32_t> run_duration = get_run_duration_();
|
const optional<uint32_t> run_duration = get_run_duration_();
|
||||||
|
@ -74,8 +74,7 @@ def range_segment_list(input):
|
|||||||
if isinstance(input, list):
|
if isinstance(input, list):
|
||||||
for list_item in input:
|
for list_item in input:
|
||||||
if isinstance(list_item, list):
|
if isinstance(list_item, list):
|
||||||
for item in list_item:
|
flat_list.extend(list_item)
|
||||||
flat_list.append(item)
|
|
||||||
else:
|
else:
|
||||||
flat_list.append(list_item)
|
flat_list.append(list_item)
|
||||||
else:
|
else:
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user