diff --git a/.github/workflows/build-beta.yml b/.github/workflows/build-beta.yml new file mode 100644 index 0000000..d814233 --- /dev/null +++ b/.github/workflows/build-beta.yml @@ -0,0 +1,117 @@ +name: Build and Publish Beta + +# Builds TEMP-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 ${{ matrix.name }} + needs: version + strategy: + matrix: + include: + # Beta serves OTA updates only, so it builds the end-user Minimal + # images, not the first-flash improv images. Artifact names match + # the on-device variant_slug: "", "-b", "2", "-b2". + - { yaml: Integrations/ESPHome/beta-channel/TEMP-1_Minimal.yaml, name: firmware-beta } + - { yaml: Integrations/ESPHome/beta-channel/TEMP-1B_Minimal.yaml, name: firmware-b-beta } + - { yaml: Integrations/ESPHome/beta-channel/TEMP-1_Minimal_R2.yaml, name: firmware2-beta } + - { yaml: Integrations/ESPHome/beta-channel/TEMP-1B_Minimal_R2.yaml, name: firmware-b2-beta } + uses: esphome/workflows/.github/workflows/build.yml@025a1e6255610c498ed590403b7e510b69e474df # 2026.4.1 + with: + files: ${{ matrix.yaml }} + esphome-version: stable + combined-name: ${{ matrix.name }} + 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 TEMP-1 beta firmware. Auto-updated on every push to the beta branch." + + - name: Rewrite manifests to absolute URLs and upload assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BASE="https://github.com/${{ github.repository }}/releases/download/beta-fw" + # Manifest names match the on-device variant_slug: "", "-b", "2", "-b2". + declare -A DIRS=( [""]=firmware-beta ["-b"]=firmware-b-beta ["2"]=firmware2-beta ["-b2"]=firmware-b2-beta ) + for v in "" "-b" "2" "-b2"; do + src="fw/${DIRS[$v]}" + man=$(find "$src" -name manifest.json | head -1) + if [ -z "$man" ]; then + echo "::error::manifest.json not found for ${DIRS[$v]}" + 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. + # The four variants have distinct device names, so bin filenames + # don't collide in the flat release-asset namespace. + jq --arg base "$BASE" ' + .builds[0].ota.path = ($base + "/" + (.builds[0].ota.path | sub(".*/"; ""))) + | .builds[0].parts |= map(.path = ($base + "/" + (.path | sub(".*/"; "")))) + ' "$man" > "manifest$v.json" + cat "manifest$v.json" + gh release upload beta-fw "manifest$v.json" -R "${{ github.repository }}" --clobber + find "$src" -name '*.bin' -print -exec \ + gh release upload beta-fw {} -R "${{ github.repository }}" --clobber \; + done + 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 }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f4a308..c5740fb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,12 +24,19 @@ jobs: pull-requests: write with: device-name: temp-1 + # The Minimal yamls are the end-user images served at firmware*/ (OTA + # updates and dashboard adoption). The improv images move to *-factory/ + # and are only used for first flashes via the web installer. yaml-files: | + Integrations/ESPHome/TEMP-1_Minimal.yaml + Integrations/ESPHome/TEMP-1B_Minimal.yaml + Integrations/ESPHome/TEMP-1_Minimal_R2.yaml + Integrations/ESPHome/TEMP-1B_Minimal_R2.yaml Integrations/ESPHome/TEMP-1.yaml Integrations/ESPHome/TEMP-1B.yaml Integrations/ESPHome/TEMP-1_R2.yaml Integrations/ESPHome/TEMP-1B_R2.yaml - firmware-names: "1:firmware,1B:firmware-b,1_R2:firmware2,1B_R2:firmware-b2" + firmware-names: "1_Minimal:firmware,1B_Minimal:firmware-b,1_Minimal_R2:firmware2,1B_Minimal_R2:firmware-b2,1:firmware-factory,1B:firmware-b-factory,1_R2:firmware2-factory,1B_R2:firmware-b2-factory" core-yaml-path: Integrations/ESPHome/Core.yaml esphome-version: stable # Bypass check if manually triggered with bypass option diff --git a/Integrations/ESPHome/.gitignore b/Integrations/ESPHome/.gitignore index d8b4157..78a2a3e 100644 --- a/Integrations/ESPHome/.gitignore +++ b/Integrations/ESPHome/.gitignore @@ -3,3 +3,4 @@ # You can modify this file to suit your needs. /.esphome/ /secrets.yaml +beta-channel/.esphome/ diff --git a/Integrations/ESPHome/Core.yaml b/Integrations/ESPHome/Core.yaml index 8e064aa..2845e14 100644 --- a/Integrations/ESPHome/Core.yaml +++ b/Integrations/ESPHome/Core.yaml @@ -1,5 +1,20 @@ substitutions: version: "26.8.27.1" + # Firmware variant identity: overridden by the TEMP-1B / R2 images so each + # hardware variant tracks its own manifests ("", "-b", "2", "-b2"). + variant_slug: "" + # 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/TEMP-1" + beta_manifest_base: "https://github.com/ApolloAutomation/TEMP-1/releases/download/beta-fw" + # Per-variant OTA manifest URLs picked by apply_ota_source. + ota_stable_manifest: "${stable_manifest_base}/firmware${variant_slug}/manifest.json" + ota_beta_manifest: "${beta_manifest_base}/manifest${variant_slug}.json" esp32: variant: esp32c3 @@ -10,13 +25,39 @@ esp32: assertion_level: SILENT enable_lwip_assert: false +esp32_ble_tracker: + id: ble_tracker + scan_parameters: + continuous: true + +bluetooth_proxy: + esphome: + # List form so package merging concatenates with each variant's own on_boot + # entries (mapping form would be replaced by the variant's block instead). on_boot: - priority: 500 - then: - - script.execute: configureSourceSensorPolling + - priority: 500 + then: + - script.execute: configureSourceSensorPolling + # 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: api: + encryption: actions: - action: play_buzzer variables: @@ -74,6 +115,17 @@ globals: captive_portal: +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 + i2c: sda: GPIO1 @@ -404,6 +456,34 @@ 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" + # OTA download needs the device awake for its whole duration. + - lambda: |- + id(deep_sleep_1).prevent_deep_sleep(); + - 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); + # Only reached if the update did not start (e.g. manifest unreachable). + # Re-arm deep sleep unless something else is holding the device awake. + - if: + condition: + and: + - switch.is_off: prevent_sleep + - binary_sensor.is_off: ota_mode + then: + - lambda: |- + id(deep_sleep_1).allow_deep_sleep(); + - platform: factory_reset disabled_by_default: True name: "Factory Reset ESP" @@ -411,6 +491,21 @@ button: 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 + - platform: template name: "Select Probe" optimistic: true @@ -487,6 +582,21 @@ switch: entity_category: "config" disabled_by_default: true + # Note: on this deep-sleep device the proxy only forwards while the device + # is awake - pair it with "Prevent Sleep" to use it continuously. + - 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: + text_sensor: - platform: wifi_info @@ -505,6 +615,18 @@ text_sensor: entity_category: "diagnostic" script: + - id: apply_ota_source + # Sets the OTA manifest URL from the Firmware Channel select (Stable/Beta). + # The manifest URLs are per-variant (ota_*_manifest substitutions via + # ${variant_slug}) so each hardware variant tracks its own manifests. + 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: configureSourceSensorPolling then: - if: diff --git a/Integrations/ESPHome/TEMP-1.yaml b/Integrations/ESPHome/TEMP-1.yaml index 86ee6e0..ff4983c 100644 --- a/Integrations/ESPHome/TEMP-1.yaml +++ b/Integrations/ESPHome/TEMP-1.yaml @@ -1,6 +1,7 @@ substitutions: source_sensor_resistance: 100kOhm - firmware_update_manifest_url: https://apolloautomation.github.io/TEMP-1/firmware/manifest.json + # Firmware variant identity: drives the OTA manifest paths in Core.yaml. + variant_slug: "" esphome: name: "apollo-temp-1" @@ -102,9 +103,6 @@ ota: - platform: http_request id: ota_managed -http_request: - verify_ssl: true - safe_mode: web_server: @@ -113,13 +111,13 @@ web_server: update: - platform: http_request - id: firmware_update + id: update_http_request name: Firmware Update - source: ${firmware_update_manifest_url} + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json wifi: on_connect: - - component.update: firmware_update + - component.update: update_http_request id: wifi_1 ap: ssid: "Apollo TEMP1 Hotspot" diff --git a/Integrations/ESPHome/TEMP-1B.yaml b/Integrations/ESPHome/TEMP-1B.yaml index 3fa23a4..bf2d999 100644 --- a/Integrations/ESPHome/TEMP-1B.yaml +++ b/Integrations/ESPHome/TEMP-1B.yaml @@ -1,6 +1,7 @@ substitutions: source_sensor_resistance: 100kOhm - firmware_update_manifest_url: https://apolloautomation.github.io/TEMP-1/firmware-b/manifest.json + # Firmware variant identity: drives the OTA manifest paths in Core.yaml. + variant_slug: "-b" esphome: name: "apollo-temp-1b" @@ -100,9 +101,6 @@ ota: - platform: http_request id: ota_managed -http_request: - verify_ssl: true - safe_mode: web_server: @@ -111,13 +109,13 @@ web_server: update: - platform: http_request - id: firmware_update + id: update_http_request name: Firmware Update - source: ${firmware_update_manifest_url} + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json wifi: on_connect: - - component.update: firmware_update + - component.update: update_http_request id: wifi_1 ap: ssid: "Apollo TEMP1B Hotspot" diff --git a/Integrations/ESPHome/TEMP-1B_BLE.yaml b/Integrations/ESPHome/TEMP-1B_BLE.yaml index 30de6fe..0eb3324 100644 --- a/Integrations/ESPHome/TEMP-1B_BLE.yaml +++ b/Integrations/ESPHome/TEMP-1B_BLE.yaml @@ -1,4 +1,5 @@ substitutions: + variant_slug: "-b" source_sensor_resistance: 100kOhm esphome: @@ -11,69 +12,71 @@ esphome: version: "${version}" min_version: 2026.8.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}" - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); - - if: - condition: - switch.is_on: notify_only_outside_temp_difference - then: - - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" - - switch.turn_on: accessory_power - - delay: 5s - - lambda: |- - id(reportAllValues).execute(); - - delay: 5s - - if: # Check If Temp Probe Is Outside Threshold - condition: - - lambda: |- - // Determine the selected probe - auto selected_probe = id(temp_probe_select).current_option(); - float current_temp; + - if: + condition: + switch.is_on: notify_only_outside_temp_difference + then: + - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" + - switch.turn_on: accessory_power + - delay: 5s + - lambda: |- + id(reportAllValues).execute(); + - delay: 5s + - if: # Check If Temp Probe Is Outside Threshold + condition: + - lambda: |- + // Determine the selected probe + auto selected_probe = id(temp_probe_select).current_option(); + float current_temp; + + if (selected_probe == "Temperature") { + current_temp = id(temp_probe).state; + } else { + current_temp = id(ntc_sensor).state; + } + + // Check temperature difference + if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { + return true; + id(last_temp) = current_temp; + } else { + return false; + } + then: # If So Then Turn On Wifi To Update + - logger.log: "Apollo Automation: Outside Temp Change Detected" + - lambda: |- + id(last_temp) = id(temp_probe).state; + else: # If Not Then Check OTA And Sleep + - if: + condition: + and: + - binary_sensor.is_off: ota_mode + - switch.is_off: prevent_sleep + then: + - deep_sleep.enter: + id: deep_sleep_1 - if (selected_probe == "Temperature") { - current_temp = id(temp_probe).state; - } else { - current_temp = id(ntc_sensor).state; - } - // Check temperature difference - if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { - return true; - id(last_temp) = current_temp; - } else { - return false; - } - then: # If So Then Turn On Wifi To Update - - logger.log: "Apollo Automation: Outside Temp Change Detected" - - lambda: |- - id(last_temp) = id(temp_probe).state; - else: # If Not Then Check OTA And Sleep - - if: - condition: - and: - - binary_sensor.is_off: ota_mode - - switch.is_off: prevent_sleep - then: - - deep_sleep.enter: - id: deep_sleep_1 - - on_shutdown: - light.turn_off: rgb_light @@ -86,18 +89,23 @@ dashboard_import: ota: - platform: esphome id: ota_esphome + - platform: http_request + id: ota_managed + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json safe_mode: wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo TEMP1 Hotspot" -bluetooth_proxy: - active: true -esp32_ble_tracker: - scan_parameters: - active: false packages: core: !include Core.yaml diff --git a/Integrations/ESPHome/TEMP-1B_BLE_R2.yaml b/Integrations/ESPHome/TEMP-1B_BLE_R2.yaml index 4aba1ee..3740a49 100644 --- a/Integrations/ESPHome/TEMP-1B_BLE_R2.yaml +++ b/Integrations/ESPHome/TEMP-1B_BLE_R2.yaml @@ -1,4 +1,5 @@ substitutions: + variant_slug: "-b2" source_sensor_resistance: 3.3kOhm packages: diff --git a/Integrations/ESPHome/TEMP-1B_Minimal.yaml b/Integrations/ESPHome/TEMP-1B_Minimal.yaml index d1076fe..af0a617 100644 --- a/Integrations/ESPHome/TEMP-1B_Minimal.yaml +++ b/Integrations/ESPHome/TEMP-1B_Minimal.yaml @@ -2,77 +2,79 @@ substitutions: source_sensor_resistance: 100kOhm esphome: - name: "apollo-temp-1b-minimal" - friendly_name: Apollo TEMP-1B Minimal - comment: Apollo TEMP-1B Minimal + name: "apollo-temp-1b" + friendly_name: Apollo TEMP-1B + comment: Apollo TEMP-1B name_add_mac_suffix: true project: - name: "ApolloAutomation.TEMP-1B_Minimal" + name: "ApolloAutomation.TEMP-1B" version: "${version}" min_version: 2026.8.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}" - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); - - if: - condition: - switch.is_on: notify_only_outside_temp_difference - then: - - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" - - switch.turn_on: accessory_power - - delay: 5s - - lambda: |- - id(reportAllValues).execute(); - - delay: 5s - - if: # Check If Temp Probe Is Outside Threshold - condition: - - lambda: |- - // Determine the selected probe - auto selected_probe = id(temp_probe_select).current_option(); - float current_temp; + - if: + condition: + switch.is_on: notify_only_outside_temp_difference + then: + - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" + - switch.turn_on: accessory_power + - delay: 5s + - lambda: |- + id(reportAllValues).execute(); + - delay: 5s + - if: # Check If Temp Probe Is Outside Threshold + condition: + - lambda: |- + // Determine the selected probe + auto selected_probe = id(temp_probe_select).current_option(); + float current_temp; - if (selected_probe == "Temperature") { - current_temp = id(temp_probe).state; - } else { - current_temp = id(ntc_sensor).state; - } + if (selected_probe == "Temperature") { + current_temp = id(temp_probe).state; + } else { + current_temp = id(ntc_sensor).state; + } + + // Check temperature difference + if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { + return true; + id(last_temp) = current_temp; + } else { + return false; + } + then: # If So Then Turn On Wifi To Update + - logger.log: "Apollo Automation: Outside Temp Change Detected" + - lambda: |- + id(last_temp) = id(temp_probe).state; + else: # If Not Then Check OTA And Sleep + - if: + condition: + and: + - binary_sensor.is_off: ota_mode + - switch.is_off: prevent_sleep + then: + - deep_sleep.enter: + id: deep_sleep_1 - // Check temperature difference - if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { - return true; - id(last_temp) = current_temp; - } else { - return false; - } - then: # If So Then Turn On Wifi To Update - - logger.log: "Apollo Automation: Outside Temp Change Detected" - - lambda: |- - id(last_temp) = id(temp_probe).state; - else: # If Not Then Check OTA And Sleep - - if: - condition: - and: - - binary_sensor.is_off: ota_mode - - switch.is_off: prevent_sleep - then: - - deep_sleep.enter: - id: deep_sleep_1 - on_shutdown: - light.turn_off: rgb_light @@ -85,12 +87,22 @@ safe_mode: ota: - platform: esphome id: ota_esphome + - platform: http_request + id: ota_managed + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json web_server: port: 80 version: 3 wifi: + on_connect: + - component.update: update_http_request id: wifi_1 ap: ssid: "Apollo TEMP1B Hotspot" diff --git a/Integrations/ESPHome/TEMP-1B_Minimal_R2.yaml b/Integrations/ESPHome/TEMP-1B_Minimal_R2.yaml index 2926114..f54392d 100644 --- a/Integrations/ESPHome/TEMP-1B_Minimal_R2.yaml +++ b/Integrations/ESPHome/TEMP-1B_Minimal_R2.yaml @@ -1,13 +1,14 @@ substitutions: + variant_slug: "-b2" source_sensor_resistance: 3.3kOhm packages: TEMP_1B_Minimal: !include "TEMP-1B_Minimal.yaml" esphome: - name: "apollo-temp-1bminimalr2" + name: "apollo-temp-1b-42" project: - name: "Apollo.TEMP-1B_Minimal_R2" + name: "Apollo.TEMP-1B_R2" version: "${version}" dashboard_import: diff --git a/Integrations/ESPHome/TEMP-1B_R2.yaml b/Integrations/ESPHome/TEMP-1B_R2.yaml index 346a1de..f068fcb 100644 --- a/Integrations/ESPHome/TEMP-1B_R2.yaml +++ b/Integrations/ESPHome/TEMP-1B_R2.yaml @@ -1,6 +1,6 @@ substitutions: source_sensor_resistance: 3.3kOhm - firmware_update_manifest_url: https://apolloautomation.github.io/TEMP-1/firmware-b2/manifest.json + variant_slug: "-b2" packages: TEMP_1B: !include "TEMP-1B.yaml" diff --git a/Integrations/ESPHome/TEMP-1_BLE.yaml b/Integrations/ESPHome/TEMP-1_BLE.yaml index 7cae602..3df56b1 100644 --- a/Integrations/ESPHome/TEMP-1_BLE.yaml +++ b/Integrations/ESPHome/TEMP-1_BLE.yaml @@ -11,68 +11,70 @@ esphome: version: "${version}" min_version: 2026.8.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}" - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); - - if: - condition: - switch.is_on: notify_only_outside_temp_difference - then: - - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" - - switch.turn_on: accessory_power - - delay: 5s - - lambda: |- - id(reportAllValues).execute(); - - delay: 5s - - if: # Check If Temp Probe Is Outside Threshold - condition: - - lambda: |- - // Determine the selected probe - auto selected_probe = id(temp_probe_select).current_option(); - float current_temp; + - if: + condition: + switch.is_on: notify_only_outside_temp_difference + then: + - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" + - switch.turn_on: accessory_power + - delay: 5s + - lambda: |- + id(reportAllValues).execute(); + - delay: 5s + - if: # Check If Temp Probe Is Outside Threshold + condition: + - lambda: |- + // Determine the selected probe + auto selected_probe = id(temp_probe_select).current_option(); + float current_temp; - if (selected_probe == "Temperature") { - current_temp = id(temp_probe).state; - } else { - current_temp = id(ntc_sensor).state; - } + if (selected_probe == "Temperature") { + current_temp = id(temp_probe).state; + } else { + current_temp = id(ntc_sensor).state; + } + + // Check temperature difference + if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { + return true; + id(last_temp) = current_temp; + } else { + return false; + } + then: # If So Then Turn On Wifi To Update + - logger.log: "Apollo Automation: Outside Temp Change Detected" + - lambda: |- + id(last_temp) = id(temp_probe).state; + else: # If Not Then Check OTA And Sleep + - if: + condition: + and: + - binary_sensor.is_off: ota_mode + - switch.is_off: prevent_sleep + then: + - deep_sleep.enter: + id: deep_sleep_1 - // Check temperature difference - if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { - return true; - id(last_temp) = current_temp; - } else { - return false; - } - then: # If So Then Turn On Wifi To Update - - logger.log: "Apollo Automation: Outside Temp Change Detected" - - lambda: |- - id(last_temp) = id(temp_probe).state; - else: # If Not Then Check OTA And Sleep - - if: - condition: - and: - - binary_sensor.is_off: ota_mode - - switch.is_off: prevent_sleep - then: - - deep_sleep.enter: - id: deep_sleep_1 - on_shutdown: - light.turn_off: rgb_light @@ -85,18 +87,23 @@ dashboard_import: ota: - platform: esphome id: ota_esphome + - platform: http_request + id: ota_managed + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json safe_mode: wifi: + on_connect: + - component.update: update_http_request ap: ssid: "Apollo TEMP1 Hotspot" -bluetooth_proxy: - active: true -esp32_ble_tracker: - scan_parameters: - active: false packages: core: !include Core.yaml nonbattery: !include NonBattery.yaml diff --git a/Integrations/ESPHome/TEMP-1_BLE_R2.yaml b/Integrations/ESPHome/TEMP-1_BLE_R2.yaml index efd792e..5b8e407 100644 --- a/Integrations/ESPHome/TEMP-1_BLE_R2.yaml +++ b/Integrations/ESPHome/TEMP-1_BLE_R2.yaml @@ -1,4 +1,5 @@ substitutions: + variant_slug: "2" source_sensor_resistance: 3.3kOhm packages: diff --git a/Integrations/ESPHome/TEMP-1_Minimal.yaml b/Integrations/ESPHome/TEMP-1_Minimal.yaml index cd4fc23..c2f077e 100644 --- a/Integrations/ESPHome/TEMP-1_Minimal.yaml +++ b/Integrations/ESPHome/TEMP-1_Minimal.yaml @@ -2,77 +2,79 @@ substitutions: source_sensor_resistance: 100kOhm esphome: - name: "apollo-temp-1-minimal" - friendly_name: Apollo TEMP-1 Minimal - comment: Apollo TEMP-1 Minimal + name: "apollo-temp-1" + friendly_name: Apollo TEMP-1 + comment: Apollo TEMP-1 name_add_mac_suffix: true project: - name: "ApolloAutomation.TEMP-1_Minimal" + name: "ApolloAutomation.TEMP-1" version: "${version}" min_version: 2026.8.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}" - - lambda: |- - id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); - - if: - condition: - or: - - binary_sensor.is_on: ota_mode - - switch.is_on: prevent_sleep - then: - - lambda: |- - ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); - id(deep_sleep_1).prevent_deep_sleep(); + - priority: 500 + then: + - text_sensor.template.publish: + id: apollo_firmware_version + state: "${version}" + - lambda: |- + id(deep_sleep_1).set_sleep_duration(id(deep_sleep_sleep_duration).state * 60 * 1000); + - if: + condition: + or: + - binary_sensor.is_on: ota_mode + - switch.is_on: prevent_sleep + then: + - lambda: |- + ESP_LOGW("Apollo", "Preventing Deep Sleep Due To OTA On Boot"); + id(deep_sleep_1).prevent_deep_sleep(); - - if: - condition: - switch.is_on: notify_only_outside_temp_difference - then: - - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" - - switch.turn_on: accessory_power - - delay: 5s - - lambda: |- - id(reportAllValues).execute(); - - delay: 5s - - if: # Check If Temp Probe Is Outside Threshold - condition: - - lambda: |- - // Determine the selected probe - auto selected_probe = id(temp_probe_select).current_option(); - float current_temp; + - if: + condition: + switch.is_on: notify_only_outside_temp_difference + then: + - logger.log: "Apollo Automation: Notify Only Outside Temp Difference" + - switch.turn_on: accessory_power + - delay: 5s + - lambda: |- + id(reportAllValues).execute(); + - delay: 5s + - if: # Check If Temp Probe Is Outside Threshold + condition: + - lambda: |- + // Determine the selected probe + auto selected_probe = id(temp_probe_select).current_option(); + float current_temp; - if (selected_probe == "Temperature") { - current_temp = id(temp_probe).state; - } else { - current_temp = id(ntc_sensor).state; - } + if (selected_probe == "Temperature") { + current_temp = id(temp_probe).state; + } else { + current_temp = id(ntc_sensor).state; + } + + // Check temperature difference + if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { + return true; + id(last_temp) = current_temp; + } else { + return false; + } + then: # If So Then Turn On Wifi To Update + - logger.log: "Apollo Automation: Outside Temp Change Detected" + - lambda: |- + id(last_temp) = id(temp_probe).state; + else: # If Not Then Check OTA And Sleep + - if: + condition: + and: + - binary_sensor.is_off: ota_mode + - switch.is_off: prevent_sleep + then: + - deep_sleep.enter: + id: deep_sleep_1 - // Check temperature difference - if (std::abs(current_temp - id(last_temp)) > id(temp_diff_threshold)->state) { - return true; - id(last_temp) = current_temp; - } else { - return false; - } - then: # If So Then Turn On Wifi To Update - - logger.log: "Apollo Automation: Outside Temp Change Detected" - - lambda: |- - id(last_temp) = id(temp_probe).state; - else: # If Not Then Check OTA And Sleep - - if: - condition: - and: - - binary_sensor.is_off: ota_mode - - switch.is_off: prevent_sleep - then: - - deep_sleep.enter: - id: deep_sleep_1 - on_shutdown: - light.turn_off: rgb_light @@ -83,7 +85,15 @@ dashboard_import: ota: - platform: esphome id: ota_esphome - + - platform: http_request + id: ota_managed + +update: + - platform: http_request + id: update_http_request + name: Firmware Update + source: ${stable_manifest_base}/firmware${variant_slug}/manifest.json + safe_mode: web_server: @@ -91,6 +101,8 @@ web_server: version: 3 wifi: + on_connect: + - component.update: update_http_request id: wifi_1 ap: ssid: "Apollo TEMP1 Hotspot" diff --git a/Integrations/ESPHome/TEMP-1_Minimal_R2.yaml b/Integrations/ESPHome/TEMP-1_Minimal_R2.yaml index aa8c4d9..d03e904 100644 --- a/Integrations/ESPHome/TEMP-1_Minimal_R2.yaml +++ b/Integrations/ESPHome/TEMP-1_Minimal_R2.yaml @@ -1,13 +1,14 @@ substitutions: + variant_slug: "2" source_sensor_resistance: 3.3kOhm packages: TEMP_1_Minimal: !include "TEMP-1_Minimal.yaml" esphome: - name: "apollo-temp-1-minimalr2" + name: "apollo-temp-1-r2" project: - name: "Apollo.TEMP-1_Minimal_R2" + name: "Apollo.TEMP-1_R2" version: "${version}" dashboard_import: diff --git a/Integrations/ESPHome/TEMP-1_R2.yaml b/Integrations/ESPHome/TEMP-1_R2.yaml index ee1063d..bca3251 100644 --- a/Integrations/ESPHome/TEMP-1_R2.yaml +++ b/Integrations/ESPHome/TEMP-1_R2.yaml @@ -1,6 +1,6 @@ substitutions: source_sensor_resistance: 3.3kOhm - firmware_update_manifest_url: https://apolloautomation.github.io/TEMP-1/firmware2/manifest.json + variant_slug: "2" packages: TEMP_1: !include "TEMP-1.yaml" diff --git a/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal.yaml b/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal.yaml new file mode 100644 index 0000000..0816da1 --- /dev/null +++ b/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of TEMP-1B_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 TEMP-1B_Minimal.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../TEMP-1B_Minimal.yaml diff --git a/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal_R2.yaml b/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal_R2.yaml new file mode 100644 index 0000000..2546d3f --- /dev/null +++ b/Integrations/ESPHome/beta-channel/TEMP-1B_Minimal_R2.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of TEMP-1B_Minimal_R2.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 TEMP-1B_Minimal_R2.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../TEMP-1B_Minimal_R2.yaml diff --git a/Integrations/ESPHome/beta-channel/TEMP-1_Minimal.yaml b/Integrations/ESPHome/beta-channel/TEMP-1_Minimal.yaml new file mode 100644 index 0000000..d557d6b --- /dev/null +++ b/Integrations/ESPHome/beta-channel/TEMP-1_Minimal.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of TEMP-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 TEMP-1_Minimal.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../TEMP-1_Minimal.yaml diff --git a/Integrations/ESPHome/beta-channel/TEMP-1_Minimal_R2.yaml b/Integrations/ESPHome/beta-channel/TEMP-1_Minimal_R2.yaml new file mode 100644 index 0000000..a2d8573 --- /dev/null +++ b/Integrations/ESPHome/beta-channel/TEMP-1_Minimal_R2.yaml @@ -0,0 +1,9 @@ +# Beta-channel build of TEMP-1_Minimal_R2.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 TEMP-1_Minimal_R2.yaml directly. +substitutions: + firmware_channel_default: "Beta" + +packages: + base: !include ../TEMP-1_Minimal_R2.yaml diff --git a/static/index.html b/static/index.html index 30470e9..6514514 100644 --- a/static/index.html +++ b/static/index.html @@ -98,11 +98,11 @@