diff --git a/README.md b/README.md index fbb6739..934451f 100644 --- a/README.md +++ b/README.md @@ -188,12 +188,34 @@ full retirement mapping this section summarises. ## Releases -- **Date-based versioning**: `vYYYY.MM.DD` (e.g. `v2026.10.08`). +- **Date-based versioning**: `vYYYY.MM.DD`, and `vYYYY.MM.DD.N` (N from 2) for a + second release in the same UTC day. - Pushes to `master` on [Gitea](https://git.notexpectedyet.com/notexpectedyet/ha-nvidia-shield) - run the release workflow (`.github/workflows/release.yml`): - - A plain push publishes the current day's release (same-day pushes are deduped). - - Bumping `version` in `custom_components/nvidia_shield/manifest.json` forces that - specific version's release before the date fallback kicks in. + run the release workflow (`.gitea/workflows/release.yml`): + - A plain push publishes today's release. If that date is already tagged it takes + the lowest free `.N`, so a gap left by an earlier failure is reused rather than + skipped. + - Setting `version` in `custom_components/nvidia_shield/manifest.json` to an + untagged value releases exactly that version instead. +- **The release commit.** When the resolved version differs from the manifest, the + workflow writes it back, commits `chore(release): v [skip ci]`, pushes it + to `master`, and tags *that* commit. It has to do this: HACS compares the installed + version against the manifest on the default branch, so a release that only ever + existed as a tag is offered to nobody (v2026.10.09 shipped that way). + - Because the tag names the bot commit, `git describe --tags` on a *developer* + commit reports the **previous** release. That is expected, not a missing tag. + - Bump it by hand instead and the tag lands on your own commit: set `version` in + the manifest, commit, push. The workflow then has nothing to write back (it sees + an untagged manifest version and tags your commit). +- **`[skip ci]`**: the bot commit carries it, and the workflow's job additionally + checks the commit *subject* (`chore(release): …`), so a write-back cannot start a + second release. Both only recognise release commits — a normal commit whose message + body merely mentions `[skip ci]` can still be skipped by Gitea's own handling, so + keep the marker out of ordinary commit messages. +- The workflow refuses to publish unless the default branch's manifest already carries + the version it is about to tag, and fails loudly (with a "bump the manifest by hand" + instruction and nothing tagged) if the write-back push is rejected, for example by + branch protection. - Releases land on Gitea with the tag and a `nvidia_shield.zip` asset for manual installs. (No automatic mirroring — see the HACS note below.) @@ -264,6 +286,73 @@ helper the flow calls is a coroutine and its dedup only runs when awaited. transport on or off - **Shield address / ADB port** — the Shield's IP, and the ADB port if you changed it from 5555 +- **Command scripts** — one optional script per command; see below + +### Command scripts (per-command overrides) + +Some commands need hardware this integration deliberately does not drive: a speaker +switch that owns the volume, an AVR input that has to move with the Shield, a mute +that has to hit two devices. For those, the Configure page takes a **script per +command**: + +| Command | Runs when | Variables passed | +| --- | --- | --- | +| `turn_on`, `turn_off` | turn on/off, from the media player *or* the remote | — | +| `volume_up`, `volume_down` | volume step | — | +| `volume_set` | absolute volume (there is no native path — the protocol has VOLUME_UP/DOWN/MUTE only) | `level` | +| `volume_mute` | mute/unmute | `is_volume_muted` | +| `select_source` | source selection (no native path) | `app_name` | + +A command with a script calls the script **instead of** the protocol/ADB path, never +both. Left unset — the default — every command behaves exactly as it did before, and +clearing a selector on the Configure page restores that. `volume_set` and +`select_source` only appear as services while their script is configured, because +there is nothing native to fall back on. + +The scripts are ordinary Home Assistant scripts. This block covers all three awkward +cases: a speaker switch owning the volume, an AVR owning the input, and a mute that +has to hit the switch as well. + +```yaml +script: + shield_volume: # select as "Volume set script" + alias: Shield volume (speaker switch) + sequence: + - action: media_player.volume_set + target: + entity_id: media_player.living_room_speaker + data: + volume_level: "{{ level }}" + shield_source: # select as "Select source script" + alias: Shield source (AVR) + sequence: + - action: media_player.select_source + target: + entity_id: media_player.avr + data: + source: "{{ app_name }}" + shield_mute: # select as "Mute script" + alias: Shield mute (speaker switch) + sequence: + - choose: + - conditions: "{{ is_volume_muted }}" + sequence: + - action: switch.turn_off + target: + entity_id: switch.living_room_speaker + default: + - action: switch.turn_on + target: + entity_id: switch.living_room_speaker +``` + +Then set `script.shield_volume` as **Volume set script**, `script.shield_source` as +**Select source script** and so on on the Configure page. Scripts are set there, not +in the YAML seed above — the seed only carries the address and the transport choice. + +A script that fails surfaces as an error on the service call that triggered it (the +command is not retried over protocol/ADB), because a silent no-op is the one outcome +that leaves you guessing. ## Entities