Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/build-beta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Build and Publish Beta

# Builds PUMP-1 firmware from the beta branch and publishes it as assets on a
# rolling "beta-fw" pre-release. The on-device "Firmware Channel" select points
# OTA updates at these assets. Stable firmware is built/published separately
# by build.yml (push to main -> GitHub Pages).

on:
push:
branches: [beta]
paths:
- 'Integrations/ESPHome/**'
workflow_dispatch:

# Least privilege: read-only by default; only publish-beta is elevated to write.
permissions:
contents: read

jobs:
version:
name: Read version
runs-on: ubuntu-latest
outputs:
v: ${{ steps.read.outputs.v }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- id: read
run: |
v=$(awk '/substitutions:/ {f=1} f && /version:/ {print $2; exit}' \
Integrations/ESPHome/Core.yaml | tr -d '"')
echo "v=$v" >> "$GITHUB_OUTPUT"
echo "Beta version: $v"

build:
name: Build firmware
needs: version
# Beta serves OTA updates only, so it builds the end-user image
# (PUMP-1_Minimal.yaml), not the first-flash improv image.
uses: esphome/workflows/.github/workflows/build.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1
with:
files: Integrations/ESPHome/beta-channel/PUMP-1_Minimal.yaml
esphome-version: stable
combined-name: firmware-beta
release-version: ${{ needs.version.outputs.v }}

publish-beta:
name: Publish beta release assets
needs: [version, build]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download firmware artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: fw
pattern: firmware*

- name: Ensure rolling 'beta-fw' pre-release exists
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release view beta-fw -R "${{ github.repository }}" >/dev/null 2>&1 \
|| gh release create beta-fw -R "${{ github.repository }}" \
--prerelease --title "Beta (rolling)" \
--notes "Latest PUMP-1 beta firmware. Auto-updated on every push to the beta branch."

- name: Rewrite manifest to absolute URLs and upload assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
BASE="https://github.com/${{ github.repository }}/releases/download/beta-fw"
man=$(find fw/firmware-beta -name manifest.json | head -1)
if [ -z "$man" ]; then
echo "::error::manifest.json not found"
exit 1
fi
echo "Rewriting $man"
# Make ota.path and parts[].path absolute release-asset URLs so the
# device never has to resolve a relative path against a redirect.
jq --arg base "$BASE" '
.builds[0].ota.path = ($base + "/" + (.builds[0].ota.path | sub(".*/"; "")))
| .builds[0].parts |= map(.path = ($base + "/" + (.path | sub(".*/"; ""))))
' "$man" > manifest.json
cat manifest.json
gh release upload beta-fw manifest.json -R "${{ github.repository }}" --clobber
find fw/firmware-beta -name '*.bin' -print -exec \
gh release upload beta-fw {} -R "${{ github.repository }}" --clobber \;
echo "Beta assets published."

- name: Point beta-fw tag at the built commit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# gh release create tags default-branch HEAD, and uploads never move
# the tag, so without this the release's source commit drifts away
# from the assets actually published.
gh api -X PATCH "repos/${{ github.repository }}/git/refs/tags/beta-fw" \
-f sha="${{ github.sha }}" -F force=true
echo "beta-fw -> ${{ github.sha }}"
Comment on lines +48 to +102

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow locations"
git ls-files | grep -E '(^|/)\.github/workflows/' || true

echo
echo "## build-beta.yml outline/text relevant section"
if [ -f .github/workflows/build-beta.yml ]; then
  nl -ba .github/workflows/build-beta.yml | sed -n '1,180p'
else
  echo ".github/workflows/build-beta.yml not found"
fi

echo
echo "## all workflow concurrency/release refs mentioning beta"
rg -n "concurrency:|cancel-in-progress:|github\.run_id|beta-fw|action-idempotency-token|GITHUB_SHA|release" .github/workflows || true

echo
echo "## git diff stat/name"
git diff --stat
git diff --name-only

Repository: ApolloAutomation/PUMP-1

Length of output: 455


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## workflow locations"
git ls-files | grep -E '(^|/)\.github/workflows/' || true

echo
echo "## build-beta.yml relevant section"
if [ -f .github/workflows/build-beta.yml ]; then
  awk '{printf "%5d: %s\n", NR, $0}' .github/workflows/build-beta.yml | sed -n '1,180p'
else
  echo ".github/workflows/build-beta.yml not found"
fi

echo
echo "## all workflow concurrency/release refs mentioning beta"
rg -n "concurrency:|cancel-in-progress:|github\.run_id|beta-fw|action-idempotency-token|GITHUB_SHA|release" .github/workflows || true

echo
echo "## git diff stat/name"
git diff --stat || true
git diff --name-only || true

Repository: ApolloAutomation/PUMP-1

Length of output: 6921


🌐 Web query:

GitHub Actions workflow concurrency workflow concurrency group cancel-in-progress same workflow branch multiple runs

💡 Result:

To manage concurrency in GitHub Actions workflows effectively, you can use the concurrency key to group runs and control whether in-progress runs are cancelled when a new run is triggered [1][2][3]. Key Concepts: Group: This defines a unique label for a concurrency group. All workflow runs that share the same group name are subjected to the same concurrency limits [1][3]. To restrict cancellation to the same workflow and the same branch (or pull request), a common and recommended practice is to use an expression incorporating both the workflow name and the ref [4][5]: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: This boolean (or expression that evaluates to one) determines the behavior when a new run enters a group that already has an active run [1][6]. - When set to false (default), new runs are queued in a pending state until the in-progress run finishes [7][5][3]. - When set to true, any currently running or pending runs within that same group are cancelled immediately, allowing the new run to proceed [1][4][3]. Conditional Cancellation: You can use expressions to dynamically set cancel-in-progress [1][8]. This is highly useful for preventing unwanted cancellations on critical branches (like main or release branches) while still automatically cancelling outdated runs on feature branches or pull requests [7][4][8]: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref!= 'refs/heads/main' }} Important Considerations: - Avoid Unique Keys: Never include values that are unique to every run, such as github.sha or github.run_id, in your group key. Doing so will create a unique group for every run, rendering the concurrency and cancellation settings ineffective [4]. - Default Behavior: Even with cancel-in-progress set to false, GitHub Actions ensures that at most one run is active and one run is pending per group; additional pending runs will cancel previous pending runs [9][6]. - Scope: If multiple workflows in your repository use the same group name, they will all share the same concurrency limit and cancellation behavior. Including ${{ github.workflow }} in your group key ensures that your concurrency settings apply only to that specific workflow [1][5][8].

Citations:


🌐 Web query:

GitHub Actions workflow concurrency group same group workflow branch runs cancelled queued cancelled older newer behavior

💡 Result:

In GitHub Actions, the concurrency group behavior depends on your configuration of the concurrency key, which ensures that only one workflow or job with a specific group name runs at any given time [1][2]. How runs are handled: 1. In-Progress Runs: If a new run starts and a previous run is already active in the same concurrency group, the behavior depends on the cancel-in-progress setting [1][3]. - When cancel-in-progress: true is set, the currently running workflow or job in that group is canceled immediately, allowing the new run to proceed [1][3]. - When cancel-in-progress: false (the default) is set, the active run is allowed to finish [3]. 2. Queued/Pending Runs: - Traditionally, GitHub Actions allowed at most one pending run in a concurrency group; if a new run was triggered while another was already waiting, the older pending run was automatically canceled and replaced by the new one [1][2][4]. - As of May 2026, you can now enable larger queues by adding queue: max to the concurrency block [4]. This allows multiple runs to wait in line and execute sequentially, rather than canceling pending runs [5][4]. Key Considerations: - Group Scope: Concurrency group names are evaluated at the repository level [1][2]. If multiple different workflows use the same group name (e.g., a shared string like my-group), they will interfere with each other and cancel/queue one another [1][6]. To scope behavior to a specific workflow or branch, it is best practice to include variables in the group name, such as ${{ github.workflow }}-${{ github.ref }} [1][6][3]. - Branch Behavior: If you include github.ref in your group name, runs on different branches will belong to different concurrency groups and will not affect each other [3]. If you use a static group name without reference to the branch, runs from all branches will share the same group and trigger cancellation or queuing across the entire repository [1][2]. Summary of cancel-in-progress vs queue: - cancel-in-progress: true: Prioritizes the newest run by killing older in-progress runs [3]. - cancel-in-progress: false (Default) + queue: max: Ensures all runs complete in sequence, preventing loss of work [4].

Citations:


Serialize beta release publication.

build-beta.yml has no concurrency guard, so multiple beta runs can upload to beta-fw and update the same Git tag in parallel. An older run finishing after a newer one can overwrite the newer manifest/binaries and repoint the tag to an older commit. Add workflow-level concurrency with a stable beta publication group name, e.g. group: ${{ github.workflow }}-${{ github.ref }}, and either cancel-in-progress: true for the latest commit or queue: max for strict serialization.

Proposed guard
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 jobs:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build-beta.yml around lines 48 - 102, Add workflow-level
concurrency to build-beta.yml using a stable beta publication group such as `${{
github.workflow }}-${{ github.ref }}`. Configure the guard to serialize or
cancel overlapping runs, ensuring only the latest beta publication can upload
assets and update the beta-fw tag; place it at the workflow level rather than
inside the publish-beta job.

8 changes: 6 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ on:

jobs:
build-and-publish:
uses: ApolloAutomation/Workflows/.github/workflows/build.yml@main
uses: ApolloAutomation/Workflows/.github/workflows/build.yml@430d90dc695c6f7d1075c4e4a0df4b13a6496252 # main 2026-07-23
permissions:
contents: write
pages: write
id-token: write
pull-requests: write
with:
device-name: pump-1
# PUMP-1_Minimal.yaml is the end-user image served at firmware/ (OTA
# updates and dashboard adoption). PUMP-1.yaml (improv + BLE) is only
# used for first flashes via the web installer.
yaml-files: |
Integrations/ESPHome/PUMP-1_Minimal.yaml
Integrations/ESPHome/PUMP-1.yaml
firmware-names: "1:firmware"
firmware-names: "1_Minimal:firmware,1:firmware-factory"
core-yaml-path: Integrations/ESPHome/Core.yaml
esphome-version: stable
# Bypass check if manually triggered with bypass option
Expand Down
103 changes: 102 additions & 1 deletion Integrations/ESPHome/Core.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
substitutions:
name: apollo-pump-1
version: "26.3.2.1"
version: "26.7.14.1"
device_description: ${name} made by Apollo Automation - version ${version}.
# Default update channel on first boot (no stored user choice yet, i.e. a
# fresh flash). The beta-channel builds override this to "Beta" (see
# Integrations/ESPHome/beta-channel/) so firmware obtained from the beta
# channel keeps tracking it instead of offering a stable "downgrade".
firmware_channel_default: "Stable"
# Manifest URL bases. Stable = GitHub Pages (main branch builds).
# Beta = rolling "beta-fw" pre-release assets (beta branch builds).
stable_manifest_base: "https://apolloautomation.github.io/PUMP-1"
beta_manifest_base: "https://github.com/ApolloAutomation/PUMP-1/releases/download/beta-fw"
# OTA manifest URLs picked by apply_ota_source.
ota_stable_manifest: "${stable_manifest_base}/firmware/manifest.json"
ota_beta_manifest: "${beta_manifest_base}/manifest.json"

esp32:
variant: esp32c6
flash_size: 8MB
framework:
type: esp-idf

esp32_ble_tracker:
id: ble_tracker
scan_parameters:
continuous: true
Comment on lines +24 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | grep -F 'Integrations/ESPHome/Core.yaml' || true

echo "== file snippet =="
if [ -f Integrations/ESPHome/Core.yaml ]; then
  nl -ba Integrations/ESPHome/Core.yaml | sed -n '1,120p'
fi

echo "== esp32_ble_tracker references =="
rg -n "esp32_ble_tracker|continuous|start_scan|stop_scan|on_turn_on|on_boot|on_state" Integrations/ESPHome/Core.yaml .github 2>/dev/null || true

Repository: ApolloAutomation/PUMP-1

Length of output: 269


🌐 Web query:

ESPHome esp32_ble_tracker continuous true starts scanning during setup documentation

💡 Result:

In ESPHome, the esp32_ble_tracker component does not start scanning immediately during the setup() method if continuous: true is configured [1]. Instead, the setup() method initializes the Bluetooth stack and FreeRTOS task, but it intentionally defers the start of the scanning process to the loop() method [1]. This design ensures that the system allows the scanner and other components to reach an idle and stable state before the scanning process begins [1]. Key technical details: - Configuration: When continuous: true is set (which is the default behavior for the component), the internal state scan_continuous_ is set to true [2][1]. - Execution: During the setup() phase, the scan_continuous_ flag is set, but the code explicitly avoids triggering an immediate scan [1]. - Loop: The loop() method, which runs repeatedly after setup, checks this flag and initiates the scanning process once it is safe to do so [3][1]. This behavior provides a safer startup sequence, preventing potential conflicts or stability issues that could arise from starting the BLE stack and scanning simultaneously with other system initialization tasks [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file section =="
awk '{ printf "%6d  ", NR } { print }' Integrations/ESPHome/Core.yaml | sed -n '1,120p'

echo "== esp32_ble_tracker references in Core =="
grep -nE "esp32_ble_tracker|continuous:|start_scan|stop_scan|on_boot|on_turn_on|on_state" Integrations/ESPHome/Core.yaml || true

echo "== deterministic YAML control-flow model =="
python3 - <<'PY'
from pathlib import Path
text = Path("Integrations/ESPHome/Core.yaml").read_text()
ops = [
    ("component setup esp32_ble_tracker", "esp32_ble_tracker:"),
    ("continuous true flag", "continuous: true"),
    ("boot action start_scan", "on_boot:"),
    ("boot action stop_scan", "stop_scan"),
    ("switch turn_on action start_scan", "on_turn_on:"),
    ("switch turn_off action stop_scan", "on_turn_off:"),
]
for name, needle in ops:
    idx = (text + "\n").index(needle + "\n", 0) if needle in text else -1
    line = text.count("\n", 0, idx) + 1 if idx != -1 else None
    print(f"{name}: line={line} found={idx != -1}")
print("order:", [(l, name) for name, line in ops if (l := text.count("\n", 0, text.index(needle))) != -1])
PY

Repository: ApolloAutomation/PUMP-1

Length of output: 5310


🌐 Web query:

ESPHome esp32_ble_tracker start_scan stop_scan start_scan action

💡 Result:

In ESPHome, the esp32_ble_tracker component provides two primary actions for manual scan control: esp32_ble_tracker.start_scan and esp32_ble_tracker.stop_scan [1][2]. These actions are typically used when the component is configured with continuous: false, allowing you to trigger scanning on demand through automations [1]. esp32_ble_tracker.start_scan Action: - Function: Initiates a Bluetooth scan [1]. - Behavior: If a scan is already in progress, the action is ignored [1]. - Configuration: You can optionally specify the continuous parameter within the action, which determines if the scan should run in continuous mode (defaults to false) [1]. Example: - esp32_ble_tracker.start_scan: continuous: true esp32_ble_tracker.stop_scan Action: - Function: Terminates the current Bluetooth scan [1]. - Behavior: Once stopped, scanning can be restarted using the start_scan action [1]. Usage Context: To use these actions, your esp32_ble_tracker configuration must generally have continuous set to false if you intend to control scanning manually rather than running it continuously [1]. This is useful for battery-saving scenarios or specific event-driven logic [1]. For instance, you might start a scan when a client connects to the API and stop it when it disconnects [1].

Citations:


🌐 Web query:

ESPHome bluetooth_proxy default state BLE Proxy switch off

💡 Result:

In ESPHome, the bluetooth_proxy component is configured with an active parameter that controls whether the proxy supports active GATT connections [1][2]. As of recent updates, the default state for the active parameter is true [3][4]. To switch off active connections (effectively returning to the previous passive-only proxy behavior), you must explicitly set the active parameter to false in your YAML configuration [1][4]: bluetooth_proxy: active: false It is important to distinguish this configuration from BLE scanning settings [5]. The bluetooth_proxy: active setting only governs the ability to support active GATT connections requested by Home Assistant; it does not autonomously connect to devices and does not impact the battery life of your BLE sensors [5]. If you are looking to reduce battery drain on sensors, you should instead look at the esp32_ble_tracker component and its scan_parameters, specifically setting active: false there, as active scanning causes sensors to transmit more frequently [1][5]. There is no native "switch" entity in ESPHome to dynamically toggle the bluetooth_proxy component on or off during runtime [6][7]. If you need to completely remove the functionality, you can use the !remove package directive in your YAML configuration [8]: packages: my_proxy:!remove Alternatively, some users have implemented custom template switches using lambda actions to modify the state of the proxy programmatically, though this is an advanced workaround and not an official feature [6].

Citations:


🌐 Web query:

ESPHome 2025.9 bluetooth_proxy switch default OFF

💡 Result:

In ESPHome versions released after the change implemented in September 2025, the bluetooth_proxy component's active parameter defaults to true [1][2]. Prior to this update, the default behavior was false (passive mode only) [3][4]. If you wish to restore the previous behavior—where active GATT connections are disabled—you must explicitly set the parameter to false in your configuration [1][2]: bluetooth_proxy: active: false It is important to distinguish this setting from the esp32_ble_tracker scan_parameters: active setting [5]. The bluetooth_proxy: active parameter only enables the capability for the proxy to support active GATT connections when requested by Home Assistant; it does not autonomously connect to devices or impact the battery life of BLE sensors [5]. Conversely, setting esp32_ble_tracker: active: true enables active scanning, which can significantly affect battery-powered devices [5].

Citations:


🌐 Web query:

ESPHome esp32_ble_tracker loop continuous scan_continuous_ start_scan

💡 Result:

In ESPHome, the esp32_ble_tracker component manages Bluetooth Low Energy scanning through its configuration parameters and available actions [1][2]. Configuration: The continuous parameter within scan_parameters determines the default scanning behavior [1][3]. - If continuous: true (default), the tracker scans indefinitely [1][3]. - If continuous: false, the tracker remains idle until an explicit start command is issued [1][2]. Actions: - esp32_ble_tracker.start_scan: This action triggers a BLE scan [1][2]. It is idempotent, meaning calling it while a scan is already in progress is ignored [1][4]. Recent updates ensure this action safely checks if the scanner is in an IDLE state before starting [4]. - esp32_ble_tracker.stop_scan: This action halts the scanning process [1][2]. Internal Mechanism: - The component maintains an internal state machine (e.g., IDLE, STARTING, RUNNING) to manage operations [4][5]. - The scan_continuous_ boolean variable is used internally by the class to track the intended operational mode (continuous vs. triggered) [6][7][8]. - The loop() method in the component's implementation ensures the scanner state is monitored and, if configured for continuous operation, restarts the scan if necessary (for example, after the BLE stack has been disabled and re-enabled) [5]. Common Use Case: On single-core chips (e.g., ESP32-C3) where the BLE tracker and Wi-Fi may conflict, a common configuration pattern is to set continuous: false and use esp32_ble_tracker.start_scan within automations (e.g., triggered by on_client_connected) to manage the scan only when needed, avoiding stability issues [1][3][2].

Citations:


Do not enable BLE scanning while the proxy switch is off.

continuous: true makes the BLE tracker scan automatically after startup. The low-priority on_boot branch stops the scan only after the default-off proxy check has already run, so BLE scanning can still occur while the switch state is OFF. Set scan_parameters.continuous to false; the existing start_scan: continuous: true actions start scanning when needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Integrations/ESPHome/Core.yaml` around lines 24 - 27, Set
esp32_ble_tracker.scan_parameters.continuous to false so BLE scanning does not
begin automatically at startup while the proxy switch is off. Preserve the
existing start_scan actions that explicitly enable continuous scanning when
needed.


bluetooth_proxy:

esphome:
on_boot:
Expand All @@ -30,6 +49,22 @@ esphome:
- switch.turn_off: stop_pump_when_full
- switch.turn_on: stop_pump_when_dry
- switch.turn_off: auto_refill
# Point the update entity at the selected channel's manifest.
- priority: -100
then:
- script.execute: apply_ota_source
# Re-apply the Bluetooth Proxy switch after all components set up, so BLE
# scanning matches the persisted switch (proxy stays off by default).
- priority: -300
then:
- if:
condition:
switch.is_on: bluetooth_proxy_switch
then:
- esp32_ble_tracker.start_scan:
continuous: true
else:
- esp32_ble_tracker.stop_scan:
- priority: -100
then:
- delay: 1000ms
Expand All @@ -43,6 +78,7 @@ esphome:
- lambda: "id(testScript).execute();"

api:
encryption:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate relevant file =="
git ls-files | grep -F 'Integrations/ESPHome/Core.yaml' || true

echo
echo "== Core.yaml lines around encryption =="
if [ -f 'Integrations/ESPHome/Core.yaml' ]; then
  nl -ba 'Integrations/ESPHome/Core.yaml' | sed -n '65,90p'
fi

echo
echo "== Inspect API component occurrences/usages in ESPHome configs =="
rg -n "api:|encryption:|key:" Integrations/ESPHome || true

echo
echo "== Search for PR title/objective references ="
git diff --stat || true

Repository: ApolloAutomation/PUMP-1

Length of output: 301


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Read Core.yaml lines around encryption =="
awk 'NR>=65 && NR<=90 { printf "%6d\t%s\n", NR, $0 }' Integrations/ESPHome/Core.yaml

echo
echo "== ESPHome API/encryption occurrences =="
grep -RInE 'api:|encryption:|key:' Integrations/ESPHome || true

echo
echo "== Read ESPHome API config context =="
grep -RInE -C 8 '^api:|  encryption:|api:' Integrations/ESPHome || true

Repository: ApolloAutomation/PUMP-1

Length of output: 2176


🌐 Web query:

ESPHome API encryption config empty encryption key API plaintext unencrypted

💡 Result:

In ESPHome, the encryption configuration for the native API is optional [1][2]. If you do not include the encryption block in your api: configuration, the API will operate in plaintext (unencrypted) mode [3]. Regarding the key parameter within the encryption block: - If the encryption block is present, a 32-byte base64-encoded key is required [1][2]. - Providing an empty encryption block or an empty key is not a valid configuration for enabling encryption [1][2]. If the key is not provided or is invalid, the API will not function securely as intended. - If you intend to use encryption, you must provide a valid 32-byte base64-encoded string [1]. In summary, if you want unencrypted, plaintext communication, simply omit the entire encryption configuration block from your api: settings [1][2]. If you want encryption, you must include the encryption block with a valid, non-empty key [1][2]. The older password authentication method is deprecated and should not be used in combination with encryption [4][5].

Citations:


Configure a valid API encryption key.

The empty encryption: block does not enable ESPHome API encryption; the API still runs plaintext unless encryption.key is provided. Add a securely provisioned 32-byte base64-encoded key to meet the encryption objective.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Integrations/ESPHome/Core.yaml` at line 81, Update the ESPHome API encryption
configuration under the encryption block to provide encryption.key with a
securely provisioned, valid 32-byte base64-encoded value. Keep the existing
encryption configuration structure and ensure the key is not empty.

actions:
- action: play_buzzer
variables:
Expand Down Expand Up @@ -83,6 +119,17 @@ web_server:
port: 80
version: 3

http_request:
verify_ssl: true
# GitHub release-asset downloads answer with a redirect carrying a
# ~3.6 KB Content-Security-Policy header; each header line must fit
# this buffer or the request fails with "HTTP_CLIENT: Out of buffer".
buffer_size_rx: 5120
# The redirect target is a signed URL with a ~850-char query string; the
# follow-up request line must fit the TX buffer or esp_http_client_open
# fails with "Out of buffer" before sending anything.
buffer_size_tx: 2048

# Buzzer
output:
- platform: ledc
Expand All @@ -96,6 +143,21 @@ button:
icon: mdi:power-cycle
name: "ESP Reboot"

- platform: template
name: "Firmware Update"
id: update_firmware
icon: mdi:cloud-download
entity_category: "config"
on_press:
- logger.log: "Applying firmware update for the selected channel"
- delay: 3s
- script.execute: apply_ota_source
- script.wait: apply_ota_source
# The manifest fetch runs in its own task; give it a fixed window to land
# (update.is_available stays false for same-version switches).
- delay: 5s
- lambda: id(update_http_request).perform(true);

Comment on lines +146 to +160

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files (selected):"
git ls-files | rg '(^|/)Core\.ya?ml$|(^|/)ESPHome/|integration' || true

echo
echo "File outline/size:"
wc -l Integrations/ESPHome/Core.yaml 2>/dev/null || true

echo
echo "Relevant sections:"
sed -n '130,175p' Integrations/ESPHome/Core.yaml 2>/dev/null || true
echo
sed -n '455,500p' Integrations/ESPHome/Core.yaml 2>/dev/null || true

echo
echo "Search for update/select/ota/manifest symbols:"
rg -n "update_http_request|apply_ota_source|update_firmware|ota|select|channel|available|force|version" Integrations/ESPHome/Core.yaml 2>/dev/null || true

Repository: ApolloAutomation/PUMP-1

Length of output: 4720


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repository files (selected):"
git ls-files | rg '(^|/)Core\.ya?ml$|(^|/)ESPHome/|integration' || true

echo
echo "File outline/size:"
wc -l Integrations/ESPHome/Core.yaml 2>/dev/null || true

 echo
echo "Relevant sections:"
sed -n '130,175p' Integrations/ESPHome/Core.yaml 2>/dev/null || true
echo
sed -n '455,500p' Integrations/ESPHome/Core.yaml 2>/dev/null || true

echo
echo "Search for update/select/ota/manifest symbols:"
rg -n "update_http_request|apply_ota_source|update_firmware|ota|select|channel|available|force|version" Integrations/ESPHome/Core.yaml 2>/dev/null || true

Repository: ApolloAutomation/PUMP-1

Length of output: 4720


🌐 Web query:

ESPHome component.update fetches manifest in separate ESP32 task perform true cached url md5

💡 Result:

In ESPHome, the http_request update component manages firmware updates by fetching a JSON manifest file from a specified URL [1][2]. On ESP32 devices, the manifest fetching process is designed to run in a separate FreeRTOS task to prevent blocking the main loop while performing network operations [3][4]. Regarding your specific technical queries: Separate ESP32 Task: The http_request update component utilizes a dedicated task (often referred to as update_task) on ESP32-based devices to perform the manifest fetch and associated HTTP operations [3][4]. This architecture allows the device to remain responsive while checking for updates [4]. Cached/MD5: The update component uses the manifest to check for new firmware versions based on metadata [1][2]. It requires the manifest to provide an MD5 hash for the firmware binary [1][5]. This MD5 hash is used during the subsequent flashing process (initiated by the user via the update.perform action) to verify the integrity of the downloaded firmware binary [4][6]. If the MD5 hash of the downloaded file does not match the value provided in the manifest, the update is rejected, and the device continues to run the existing firmware [6]. The update component itself does not inherently implement generic HTTP-level caching for the manifest file, though standard network stack behavior may apply. Note that caching mechanisms (like ETag or Last-Modified) are more explicitly documented for other components, such as online_image [7][8]. If you are encountering issues with this process, such as failed fetches at startup, it is often because the component attempts to reach the network before the connection is fully established [9]. This has been addressed in recent updates to ensure checks are skipped if the network is not yet connected [9].

Citations:


Synchronize manifest refresh before forcing installation.

component.update: update_http_request schedules the manifest fetch in a separate ESP32 task, so the five-second wait can expire while the fetch is still running. perform(true) will then install from whatever manifest metadata is currently committed, which may still belong to the previously selected Stable channel after the user changed the select to Beta. Use a completion callback/state machine before forcing the install, and apply the same pattern to the on_value path for firmware_channel.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Integrations/ESPHome/Core.yaml` around lines 146 - 160, Update the Firmware
Update flow around update_firmware and the firmware_channel on_value handler so
manifest retrieval is synchronized through a completion callback or explicit
state machine before invoking update_http_request.perform(true). Remove reliance
on the fixed five-second delay, ensure installation uses metadata fetched for
the newly selected channel, and apply the same synchronization behavior to both
update triggers.

- platform: factory_reset
disabled_by_default: True
name: "Factory Reset ESP"
Expand Down Expand Up @@ -220,6 +282,19 @@ switch:
id(pump_start_time) = 0;
id(safety_alert_active) = false;

- platform: template
name: "Bluetooth Proxy"
id: bluetooth_proxy_switch
icon: mdi:bluetooth
entity_category: "config"
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
on_turn_on:
- esp32_ble_tracker.start_scan:
continuous: true
on_turn_off:
- esp32_ble_tracker.stop_scan:

binary_sensor:
- platform: status
name: Online
Expand Down Expand Up @@ -384,7 +459,33 @@ text_sensor:
update_interval: never
entity_category: "diagnostic"

select:
- platform: template
name: "Firmware Channel"
id: firmware_channel
icon: mdi:source-branch
entity_category: "config"
optimistic: true
restore_value: true
options:
- "Stable"
- "Beta"
initial_option: "${firmware_channel_default}"
on_value:
then:
- script.execute: apply_ota_source

script:
- id: apply_ota_source
# Sets the OTA manifest URL from the Firmware Channel select (Stable/Beta).
then:
- lambda: |-
const bool beta = id(firmware_channel).current_option() == "Beta";
std::string url = beta ? "${ota_beta_manifest}" : "${ota_stable_manifest}";
ESP_LOGI("firmware", "OTA manifest set to: %s", url.c_str());
id(update_http_request).set_source_url(url);
- component.update: update_http_request

- id: pumpUntilFull
then:
- switch.turn_on: stop_pump_when_full
Expand Down
9 changes: 3 additions & 6 deletions Integrations/ESPHome/PUMP-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ esphome:
name: "ApolloAutomation.PUMP-1"
version: "${version}"

min_version: 2023.11.1
min_version: 2025.11.0

dashboard_import:
package_import_url: github://ApolloAutomation/PUMP-1/Integrations/ESPHome/PUMP-1_Minimal.yaml
Expand All @@ -38,20 +38,17 @@ ota:
- platform: http_request
id: ota_managed

http_request:
verify_ssl: true

safe_mode:

update:
- platform: http_request
id: firmware_update
id: update_http_request
name: Firmware Update
source: https://apolloautomation.github.io/PUMP-1/firmware/manifest.json

wifi:
on_connect:
- component.update: firmware_update
- component.update: update_http_request
ap:
ssid: "Apollo PUMP-1 Hotspot"

Expand Down
26 changes: 20 additions & 6 deletions Integrations/ESPHome/PUMP-1_Minimal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ esphome:
name: "ApolloAutomation.PUMP-1"
version: "${version}"

min_version: 2023.11.1
min_version: 2025.11.0
# List form so package merging concatenates with Core.yaml's on_boot
# entries (mapping form would replace Core's whole list instead).
on_boot:
priority: 500
then:
- text_sensor.template.publish:
id: apollo_firmware_version
state: "${version}"
- priority: 500
then:
- text_sensor.template.publish:
id: apollo_firmware_version
state: "${version}"

dashboard_import:
package_import_url: github://ApolloAutomation/PUMP-1/Integrations/ESPHome/PUMP-1_Minimal.yaml
Expand All @@ -22,8 +24,20 @@ dashboard_import:
ota:
- platform: esphome
id: ota_esphome
- platform: http_request
id: ota_managed

safe_mode:

update:
- platform: http_request
id: update_http_request
name: Firmware Update
source: https://apolloautomation.github.io/PUMP-1/firmware/manifest.json

wifi:
on_connect:
- component.update: update_http_request
ap:
ssid: "Apollo PUMP-1 Hotspot"

Expand Down
9 changes: 9 additions & 0 deletions Integrations/ESPHome/beta-channel/PUMP-1_Minimal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Beta-channel build of PUMP-1_Minimal.yaml: the identical image except the Firmware
# Channel select defaults to "Beta" on first boot, so firmware obtained from
# the beta channel keeps tracking it. Built by build-beta.yml only; the
# stable (GitHub Pages) builds use PUMP-1_Minimal.yaml directly.
substitutions:
firmware_channel_default: "Beta"

packages:
base: !include ../PUMP-1_Minimal.yaml
2 changes: 1 addition & 1 deletion static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
<h1>Apollo PUMP-1 Installer</h1>

<p class="button-row" align="center">
<esp-web-install-button manifest="./firmware/manifest.json">
<esp-web-install-button manifest="./firmware-factory/manifest.json">
</esp-web-install-button>
</p>

Expand Down
Loading