PyLoxone-Audio 0.1.0__tar.gz → 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. pyloxone_audio-0.1.1/API.md +123 -0
  2. pyloxone_audio-0.1.1/DIAGNOSTICS.md +203 -0
  3. pyloxone_audio-0.1.1/EXACT-TRACK-RETURN.md +94 -0
  4. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/MANIFEST.in +4 -0
  5. pyloxone_audio-0.1.1/NATIVE-MODES.md +96 -0
  6. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/NOTICE.md +7 -5
  7. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PKG-INFO +56 -18
  8. pyloxone_audio-0.1.1/POSITION-RESTORE.md +99 -0
  9. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PyLoxone_Audio.egg-info/PKG-INFO +56 -18
  10. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PyLoxone_Audio.egg-info/SOURCES.txt +10 -1
  11. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/README.md +55 -17
  12. pyloxone_audio-0.1.1/SPOTIFY.md +102 -0
  13. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/__init__.py +11 -15
  14. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/catalogue.py +37 -1
  15. pyloxone_audio-0.1.1/pyloxone_audio/diagnostic_events.py +276 -0
  16. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/metadata.py +12 -0
  17. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/native.py +166 -17
  18. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/selection.py +7 -45
  19. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/sources.py +124 -29
  20. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyproject.toml +1 -1
  21. pyloxone_audio-0.1.1/tests/library/test_audio_modes.py +301 -0
  22. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_boundary_qualification.py +1 -47
  23. pyloxone_audio-0.1.1/tests/library/test_diagnostic_events.py +178 -0
  24. pyloxone_audio-0.1.1/tests/library/test_diagnostic_wire.py +105 -0
  25. pyloxone_audio-0.1.1/tests/library/test_exact_track_start.py +67 -0
  26. pyloxone_audio-0.1.1/tests/library/test_portable_policies.py +12 -0
  27. pyloxone_audio-0.1.1/tests/library/test_restore_readiness.py +191 -0
  28. pyloxone_audio-0.1.0/API.md +0 -86
  29. pyloxone_audio-0.1.0/SPOTIFY.md +0 -260
  30. pyloxone_audio-0.1.0/pyloxone_audio/spotify.py +0 -88
  31. pyloxone_audio-0.1.0/tests/library/test_portable_policies.py +0 -33
  32. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/LICENSE +0 -0
  33. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PyLoxone_Audio.egg-info/dependency_links.txt +0 -0
  34. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PyLoxone_Audio.egg-info/requires.txt +0 -0
  35. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/PyLoxone_Audio.egg-info/top_level.txt +0 -0
  36. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/announcement.py +0 -0
  37. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/clock.py +0 -0
  38. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/models.py +0 -0
  39. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/py.typed +0 -0
  40. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/pyloxone_audio/radio.py +0 -0
  41. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/setup.cfg +0 -0
  42. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/conftest.py +0 -0
  43. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/fixtures/audioserver/README.md +0 -0
  44. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/fixtures/audioserver/sebastian_alarm_end.txt +0 -0
  45. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/fixtures/audioserver/sebastian_loxone_radio.json +0 -0
  46. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/fixtures/audioserver/sebastian_tts.json +0 -0
  47. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/fixtures/audioserver/sebastian_tunein.json +0 -0
  48. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/pytest.ini +0 -0
  49. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_announcement.py +0 -0
  50. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_connection.py +0 -0
  51. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_events.py +0 -0
  52. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_library_source.py +0 -0
  53. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_metadata.py +0 -0
  54. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_playback.py +0 -0
  55. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_seek.py +0 -0
  56. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_timing.py +0 -0
  57. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_audioserver_wire.py +0 -0
  58. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_boundary_environment.py +0 -0
  59. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_boundary_ownership.py +0 -0
  60. {pyloxone_audio-0.1.0 → pyloxone_audio-0.1.1}/tests/library/test_distribution_dependencies.py +0 -0
@@ -0,0 +1,123 @@
1
+ # PyLoxone-Audio 0.1.1 — API and compatibility
2
+
3
+ A separately installable Apache-2.0 Python library containing the concrete native
4
+ audio implementation used by one existing PyLoxone integration. This is not a
5
+ second integration, new login, facade-only prototype or claim of upstream automatic
6
+ adoption. Public availability is verified separately by the release production gate. See NOTICE.md for attribution/provenance.
7
+
8
+ ## Concrete public API and ownership
9
+
10
+ The explicit exports in pyloxone_audio/__init__.py are the public API:
11
+
12
+ - AudioServerCommands(host, current_credentials) owns bounded native command
13
+ authentication, correlation, cancellation, close and uncertain-write fences.
14
+ The callback supplies the current username/token; there is no token persistence.
15
+ - AudioServerListener and AudioEventReceiver receive/parse exact-player metadata;
16
+ AudioZoneMetadata is the neutral snapshot. AudioMetadataCoordinator supports
17
+ exact-player subscriptions, optional host task scheduling, start and awaited stop.
18
+ - AudioCatalogue(commands, player_id), CatalogueTarget, ConfiguredSource and
19
+ AudioFavourite own discovery, opaque browser targets, last-good source history,
20
+ configured favourites, provider/account qualification and neutral search values.
21
+ - MediaNode, SearchResult and MediaKind contain no HA entity or UI dependency.
22
+ - async_select_catalogue, async_select_favourite, async_select_radio_station and
23
+ async_native_catalogue_noop own source dispatch, settlement and fresh exact-room
24
+ provider feedback. Browser selection remains strict exact-item proof. Source
25
+ return for a saved exact item requires matching track feedback, and may attempt
26
+ at most one optional best-effort position restore after bounded seek readiness. Optional failure does not
27
+ undo confirmed playback; a fixed warning is emitted, never exception payloads.
28
+ - async_source_route owns native/retained routing and safety decisions; SourceRoute
29
+ and SourceContentRequiredError describe its outcomes. Hosts provide lifecycle/
30
+ newest-request liveness callbacks and snapshot bootstrap. There is no HA-provider
31
+ bridge callback. Native identity checks, uncertain-write fences and cancellation
32
+ remain; no blind replay/retry or substitute content is introduced.
33
+ - manual_seek_supported, manual_seek_position, native_track_identity,
34
+ ManualSeekQualification and async_seek_track retain strict current-track/manual
35
+ seek capability, account/identity, echo and observed-progress qualification.
36
+ - passive_history_eligible and confirmed_library_session own reusable native
37
+ history eligibility. Genuine ordered observed feedback is distinct from intent.
38
+ - async_send_prepared_announcement handles prepared bounded WAV bytes through
39
+ the native event operation; the host supplies an existing lazy HTTP session.
40
+ - audioserver_uri and parse_audio_events retain their concrete native semantics.
41
+
42
+ SourceEnvironment(clock=callable) is immutable per-consumer clock configuration.
43
+ Pass environment= to source/seek/routing functions when injecting a clock.
44
+ The callable must return aware UTC datetimes in the metadata.received_at domain.
45
+ The default is the library's aware UTC clock, not HA or another consumer's state.
46
+ Its async_observed_change method executes the concrete native settlement algorithm;
47
+ asyncio deadlines remain monotonic. No process-wide clock replacement is required.
48
+
49
+ The host retains HA setup/entities/registries/services, newest-request tasks and
50
+ unload cancellation, existing native authentication, error translation, artwork and
51
+ HA media/TTS preparation. It must keep liveness checks valid across awaits, drain
52
+ owned tasks before teardown and close commands before metadata/token owners.
53
+ ManualSeekQualification must be checked against actual current native metadata
54
+ and the host revision/duration, not merely a cached UI value. Optional native
55
+ completion requires settled dispatch and fresh exact-room/provider/account proof.
56
+
57
+ ## Native modes
58
+
59
+ `AudioRepeatMode` (OFF=0, ALL=1, ONE=3) and `audio_mode_supported` are public
60
+ HA-independent exports. `AudioZoneMetadata` adds nullable `shuffle`, typed
61
+ `repeat` and connection-qualified `shuffle_switch_supported` state.
62
+ `AudioServerCommands.async_set_shuffle(player_id, bool, *, is_current=...)` and
63
+ `async_set_repeat(player_id, AudioRepeatMode, *, is_current=...)` send exactly one
64
+ explicit native player request through existing authentication/serialization and
65
+ strictly validate its reply. The host qualifies fresh native permissions/state
66
+ and maintains its liveness callback; no Spotify bridge/API or optimistic metadata
67
+ write is involved. See [NATIVE-MODES.md](NATIVE-MODES.md) for firmware, partial
68
+ repeat capability, standard HA mapping and unknown/lifecycle semantics.
69
+
70
+ ## Optional structured diagnostics (new 0.1.1 increment)
71
+
72
+ `pyloxone_audio.diagnostic_events` defines the version1 fixed-field schema,
73
+ `DiagnosticEmitter`, `diagnostics_enabled` and exception-isolating
74
+ `diagnostic_event`. `AudioServerCommands(..., diagnostics=None)` and
75
+ `AudioMetadataCoordinator.diagnostics` accept an optional observer. Its `enabled`
76
+ must be literally true; its synchronous `emit(stage, **fields)` must only enqueue,
77
+ never await/write files. Callers gate detailed construction before invoking it.
78
+ The emitter allowlists fields/routes and keyed pseudonyms before calling its sink.
79
+ Foreign observer exceptions are isolated. Authentication data never enters events.
80
+
81
+ The HA adapter supplies `EntryAudioDiagnostics`/`RollingJournal`, one diagnostic
82
+ switch and the existing admin config-entry download. These are host internals,
83
+ not a portable HA/storage dependency or a second integration. Separate HA Store
84
+ preference/salt avoids reload-on-options. See [DIAGNOSTICS.md](DIAGNOSTICS.md) for
85
+ the supported enable/export workflow, exact bounds and evidence/privacy limits.
86
+
87
+ This narrowly supersedes the earlier blanket instrumentation exclusion. The old
88
+ `pyloxone_audio.diagnostics`, `SourceDiagnostics`, `trace_source_action`, raw
89
+ snapshot logging and `last_source_failure` API remain removed, not compatibility
90
+ stubs. No source/playback, authentication, fence, clock or retry policy changes.
91
+
92
+ ## Tested boundary and installation
93
+
94
+ Only the canonical Python 3.14.7 / HA 2026.9.1 / websockets 15.0.1 /
95
+ pycryptodome 4.0.1 / aiohttp 3.14.3 environment is exercised here. The metadata
96
+ requires Python >=3.14 but is not a wider tested compatibility promise. HA-free
97
+ portable runs explicitly reject HA imports. External I/O in tests is synthetic;
98
+ AF_UNIX wire tests use actual websocket/crypto implementations with TCP blocked.
99
+ No all-version/firmware/room or universal physical playback claim follows from these tests.
100
+
101
+ For Python consumers, install the public named pin `PyLoxone-Audio==0.1.1` in a
102
+ supported isolated environment. The release production gate checks both public
103
+ wheel and sdist resolution, dependencies, imports and runtime identity. A local
104
+ `--no-deps` wheel installation is only an artifact test, not public resolution.
105
+ The matching host manifest pins that version while retaining domain `loxone`,
106
+ name PyLoxone and integration version 0.10.2. For HA, use the complete matching
107
+ adapter/preview and normal host dependency handling, not a manual live pip step.
108
+
109
+ The matching library and adapter passed independent source and exact-wheel review, and the common pair was installed on Home Assistant OS (Core 2026.10.4, Python 3.14.9). On 28 October 2026 the owner accepted two normal-use Local Library → Spotify returns to the same track near its saved position, supported by integration command and native content/position feedback. This is one-room confirmation, not all-room acoustic, whole-queue, persistent shuffle or restart-recovery proof.
110
+
111
+ For public availability, use the matching GitHub release, PyPI version and release verification receipts. Source, tag or uploader success alone is not the production gate. Existing Home Assistant installations do not automatically update.
112
+
113
+ ## Intentional 0.1.1 retirement
114
+
115
+ The optional SpotifySessionBridge, SpotifyNativeSession,
116
+ NativeSessionUnavailableError, NativeResumeOutcome, async_optional_native_resume
117
+ and async_resume_native_session API and its bridge parameter are removed together
118
+ with the HA-only controller. Consumers using those optional interfaces must update
119
+ alongside the matching 0.1.1 adapter pin; no compatibility facade silently retains
120
+ provider access. The native command/authentication/metadata/Browse/manual-seek API
121
+ remains. previous_source_labels is the room-local playable-content view; configured
122
+ presence is still source_descriptors, not evidence of selected/played content.
123
+ See SPOTIFY.md and POSITION-RESTORE.md for the current function map and limits.
@@ -0,0 +1,203 @@
1
+ # Opt-in audio diagnostics — 0.1.1
2
+
3
+ This feature is part of the matching PyLoxone 0.10.2 adapter and PyLoxone-Audio
4
+ 0.1.1 release pair, not the older 0.1.0 release. Installing the wheel alone does
5
+ not add the Home Assistant switch. The matching pair has been privately installed
6
+ on HAOS (Core 2026.10.4, Python 3.14.9); owner-enabled collection and a downloaded
7
+ diagnostic report have been observed. This is not public availability, upstream
8
+ approval, a universal UI/device test or a physical playback repair.
9
+
10
+ The final same-track/native-mode pair has completed independent review and
11
+ common private adoption. The owner subsequently accepted two one-room returns
12
+ with supporting saved-session, exact command, content and position feedback.
13
+ That is bounded confirmation, not all-room acoustic or queue proof.
14
+
15
+ Defaults, switch preferences, retention, storage/export bounds and schema version1
16
+ remain unchanged. Its narrowly extended allowlist records native aliased
17
+ `shuffle/enable|disable`, `repeat/0|1|3`, standard HA `shuffle_set`/`repeat_set`
18
+ requests and validated feedback fields `shuffle` (literal boolean) and `repeat`
19
+ (`off|all|one`). Unknown mode fields are omitted, never inferred as off; malformed
20
+ routes/enums/booleans remain rejected. A command reply is not physical acceptance.
21
+ No raw mode payload, credential or provider state is exported.
22
+ During a bounded readiness wait, `seek_unsupported` is a
23
+ current decision, not proof the source can never seek. A later `eligible` requires
24
+ positive capability on the same valid target; a source-transport `timeout` with
25
+ `seek_unsupported` means playback was retained without a seek. No prior raw native
26
+ capability mask can be reconstructed from a false diagnostic boolean.
27
+
28
+ ## Tester workflow
29
+
30
+ 1. In Home Assistant **Settings > Devices & services > Entities**, filter to your
31
+ Loxone integration entry and find **Audio diagnostics**. It is a diagnostic
32
+ entity, hidden from automatic dashboards, not disabled. Turn it **on**. With
33
+ several entries each has its own switch and independent report.
34
+ 2. Reproduce the problem through your usual native UI or controlling app. Note
35
+ local time/timezone, the room, source sequence, expected result and what you
36
+ actually saw/heard. Do not repeatedly trigger a failure merely to fill a report.
37
+ 3. Open **Settings > Devices & services > Loxone**, use the relevant entry's
38
+ three-dot menu and **Download diagnostics**. This uses HA's existing
39
+ administrator-only config-entry diagnostic download, not a new public endpoint.
40
+ 4. Turn **Audio diagnostics off**. That stops new collection, not the integration
41
+ or playback. No reload, new connection, Core restart, media command or bookmark
42
+ reset occurs when switching it on/off. The explicit setting survives ordinary
43
+ restarts once persistence is confirmed; new installs/upgrades without a valid
44
+ saved setting start **off**. OFF stops emission immediately, before storage I/O.
45
+ Check the switch/report health: `pending_preference` is the latest unconfirmed
46
+ requested bool (or null when confirmed), and `preference_restart_risk` is true
47
+ while pending. `storage_unavailable` plus a sticky `storage_failures` count
48
+ reports failed, mismatched, deferred or cancelled persistence. A successful
49
+ journal flush/export does not clear that pending preference error.
50
+ 5. Review the downloaded JSON before attaching it to your agreed issue/report
51
+ channel. Nothing uploads automatically. Include the exact candidate/ZIP hash,
52
+ HA/PyLoxone/library versions, symptom time/timezone, steps and audible versus
53
+ displayed result. Locally match a room to its `room`/`player` aliases if needed;
54
+ do not post original household IDs or private account details. Do not contact
55
+ maintainers/testers until your own agreed outreach/publication gate is met.
56
+
57
+ The switch and actual HA admin download handler have been exercised through an
58
+ offline owning-HA harness, then installed and checked on the real HAOS runtime.
59
+ An owner subsequently enabled collection and supplied an actual downloaded report
60
+ containing send/completion/reply, saved-session and restore-decision evidence.
61
+ The supplied report had no storage, observer or queue-loss errors. This validates
62
+ that live collection/export journey, not all branches or audible playback.
63
+ Menu placement may vary with HA. The private report is not a public fixture.
64
+
65
+ ## Reading a report and preparing a useful bug report
66
+
67
+ - Check `enabled` and `status`: `collecting` means the feature was enabled when
68
+ exported. It is independent of the integration's existing experimental
69
+ play/pause option, which gates native Audio Server playback features rather
70
+ than diagnostic recording. Turning diagnostics on does not enable those controls.
71
+ - Check `coverage_oldest_utc`, `coverage_newest_utc` and `export_oldest_utc` against
72
+ the incident time. The report cannot reconstruct commands from before collection
73
+ started. A short window immediately after enabling is normal.
74
+ - Check `dropped_queue`, `dropped_storage`, `export_omitted_records`,
75
+ `storage_failures` and `observer_failures`. Nonzero counters qualify what can be
76
+ inferred; zero counters do not prove every external/native event was received.
77
+ - `complete_24h: false` is deliberately conservative, not by itself a fault.
78
+ Retention is up to 24 hours; it is not a promise of uninterrupted or complete
79
+ reception, and the bounded download may be shorter than the stored window.
80
+ - Follow the same request/player/source aliases through `request`,
81
+ `send_attempt`, `send_completed` and `reply` or an error/timeout. Use
82
+ `session_saved` and `restore_decision` to distinguish a missing bookmark, an
83
+ ineligible seek and an actual seek attempt. A `restore_echo` is not proof the
84
+ loudspeaker playback moved; report what was heard separately.
85
+ - Include the local incident time and timezone, intended room/source, exact
86
+ versions/build, normal UI steps, and expected versus observed behaviour.
87
+ Review the complete JSON before attaching it manually. Do not post private
88
+ room/account mappings, credentials or unrelated raw exports.
89
+
90
+ Turning collection off preserves unexpired evidence, so a report can also be
91
+ downloaded after disabling it. Do not assume `enabled: false` means a retained
92
+ timeline is empty. Check the actual event times and collection status instead.
93
+
94
+ ## Retention and limits
95
+
96
+ Collection continues while on: **24 hours is a rolling maximum record age, not a
97
+ collection timeout**. Off retains unexpired evidence for reporting and preserves
98
+ configuration. Disabled housekeeping runs approximately every 60 seconds;
99
+ export filters expiry too. Timestamps use UTC/elapsed seconds, including DST.
100
+ Already expired records are not recovered after restart or a coverage gap.
101
+
102
+ Preference saves use HA's existing Store and public readback, checking the exact
103
+ requested bool and this entry's pseudonym key without exporting the key. The
104
+ existing housekeeping writer retries the latest pending request at most once per
105
+ 60 seconds (monotonic backoff), outside the journal lock. It never replays an old
106
+ ON over a newer OFF; closing retires retry ownership. HA stopping/deferred writes
107
+ are not treated as durable confirmation. Once storage recovers and save/readback
108
+ succeed, pending clears and ordinary `off`/`collecting` health returns. While a
109
+ failed OFF remains unpersisted, an actual restart can restore the earlier saved
110
+ ON. Storage that cannot write cannot promise durable OFF: resolve the reported
111
+ disk/permission problem and confirm pending has cleared before relying on restart.
112
+
113
+ Per integration entry:
114
+
115
+ - Queue: at most **256 events**; overflow removes the oldest queued record.
116
+ - Sanitised event: at most **2 KiB**; no unbounded raw frames or DEBUG logs.
117
+ - Timeline: at most **4 MiB**, in **256 KiB** segments. Size pressure rotates the
118
+ oldest segment, potentially shortening coverage to much less than 24 hours.
119
+ - Atomic maintenance reserves at most one additional segment plus bounded counter
120
+ metadata (8 KiB read bound). Preference/pseudonym-key storage is separate and
121
+ tiny. Local files are private, outside web/static paths.
122
+ - Export: newest records within a bounded **1 MiB** audio-report budget. The
123
+ adapter reserves **64 KiB** for HA's separate envelope. HA owns that envelope;
124
+ unusually large platform metadata is not under this integration's control.
125
+ Exports state their omission count rather than promising every retained record.
126
+
127
+ Coverage oldest/newest times, export oldest time, mapped/omitted room aliases,
128
+ expiry/rotation/invalid counts and queue/storage/observer failures accompany the
129
+ report. `complete_24h` is conservatively false: a timestamp range is not proof of
130
+ unbroken reception. Restarted sequence/monotonic values belong to a new process;
131
+ UTC and transport/request/player aliases provide correlation, not unique physical
132
+ causality. A reported storage failure warns once with fixed advice to check disk
133
+ space/permissions; it never includes exception text or interrupts playback.
134
+
135
+ File I/O runs in the existing HA executor, through one entry-owned writer and
136
+ serial lock. Cancellation waits for an in-progress filesystem operation before
137
+ releasing that lock. This cannot cancel a stuck OS filesystem operation. A slow
138
+ or full disk can lose diagnostics, not backpressure native playback. Stored
139
+ partial/invalid event lines are rejected; atomic replacement preserves preceding
140
+ complete records where possible. A report can honestly have no recoverable data.
141
+ Startup and ordinary maintenance (even cached/OFF maintenance) discard abandoned
142
+ `events-<numeric index>.jsonl.tmp` files with the journal's minimum 16-digit index
143
+ and `state.json.tmp`; uncommitted payloads are not evidence. Cleanup never reads
144
+ these payloads or follows symlinks, leaves unrelated names untouched, inspects at
145
+ most 256 directory entries and removes at most 64 recognised regular temporary
146
+ files per operation. Unexpected types, work limits or unlink errors report storage
147
+ failure rather than claiming recovery. Successful recovery/rotation restores the
148
+ timeline plus one atomic segment and small metadata allowance above; foreign files
149
+ or an inaccessible directory cannot be claimed bounded or successfully cleaned.
150
+
151
+ ## What the evidence means
152
+
153
+ - `ha_service_request` is HA's request event for an exact registered audio entity;
154
+ area/device-only targeting and unrelated controls are not reconstructed.
155
+ - `session_saved` samples the actual qualified room/source cache, not Recorder or
156
+ a separately recreated bookmark. `restore_decision` reports the first evaluated
157
+ guard reason, saved/current numeric positions/durations and initial/post-auth
158
+ phase; unevaluated guards are not falsely described as failures.
159
+ - Audio Server `request`, `authenticated`, `send_attempt`, `send_completed` and
160
+ `reply` distinguish authentication, the actual existing socket send await and
161
+ a parsed native result. Source confirmation can instead settle from separately
162
+ qualified requested-source feedback. A completed send is **not device execution**.
163
+ - `restore_echo` is a numeric native position echo, **not acoustic movement** or
164
+ proof that playback advanced from the requested position. Optional-restore
165
+ failure leaves confirmed source success intact; no retry is introduced.
166
+ - Miniserver sends are recorded on its existing connection **only** for discovered
167
+ audio controls and allowlisted play/pause/stop/next/previous/volume operations.
168
+ Encrypted command bytes/authentication remain untouched; no auth envelope enters
169
+ the sink. Relevant numeric volume/play-state feedback is labelled separately
170
+ from Audio Server track/source/position/event feedback and disconnects.
171
+
172
+ The same opt-in policy covers all AudioZoneV2 players discovered in that loaded
173
+ entry; there are no hardcoded household room IDs. Report aliases correlate routes,
174
+ player, room, account, content and parent context without exposing originals.
175
+ Meaningful non-secret seek numbers and semantic route structure remain exact.
176
+ No title, private announcement text, raw house structure, arbitrary frame/error,
177
+ credential, cookie, token, password or raw media URL is persisted by this feature.
178
+
179
+ HA itself adds standard system/custom-integration metadata to diagnostic downloads,
180
+ which can include installation/platform/hostname details. That HA-owned envelope
181
+ is **not** claimed to be anonymous. Review the actual entire download before
182
+ sharing; pseudonymisation of the audio portion is not universal anonymisation.
183
+
184
+ ## Scope and cost
185
+
186
+ The library supplies no-op-by-default structured events and imports no HA/storage
187
+ implementation. The existing adapter owns the switch, preference, local writer
188
+ and export. There is no sidecar, second integration, new socket, global debug
189
+ setting or automatic upload. Disabled callers skip detailed diagnostic field
190
+ construction and queue/file writes; expiry of previously retained data still runs.
191
+
192
+ Synthetic HA-harness measurements are retained with this candidate's development
193
+ receipts: process CPU deltas, `/proc/self/status` RSS/HWM, event rate, actual journal
194
+ bytes and export bytes/time. They are a short synthetic load, not quiet-house or
195
+ busy-house measurements, device latency proof, a per-day write forecast, or an
196
+ incremental production Core memory estimate. Limits are fixed bounds, not a promise
197
+ that a busy installation will retain a full day.
198
+
199
+ This capability cannot retrieve commands from before enablement or retroactively
200
+ diagnose earlier Spotify returns. It changes no source, queue, seek, retry, newest
201
+ request, authentication or control policy. Existing independently operated private
202
+ rolling observers/watchdogs are neither imported, replaced nor stopped. Their
203
+ records are not included in this report, and their settings remain independent.
@@ -0,0 +1,94 @@
1
+ # Exact remembered-track return — 0.1.1
2
+
3
+ The matching library and adapter passed independent source and exact-wheel review, and the common pair was installed on Home Assistant OS (Core 2026.10.4, Python 3.14.9). On 28 October 2026 the owner accepted two normal-use Local Library → Spotify returns to the same track near its saved position, supported by integration command and native content/position feedback. This is one-room confirmation, not all-room acoustic, whole-queue, persistent shuffle or restart-recovery proof.
4
+
5
+ For public availability, use the matching GitHub release, PyPI version and release verification receipts. Source, tag or uploader success alone is not the production gate. Existing Home Assistant installations do not automatically update.
6
+ Use the exact matching release wheel, adapter, preview and checksums; historical
7
+ same-version development artifacts are not interchangeable.
8
+
9
+ ## What this correction does
10
+
11
+ - Individual Spotify **track and podcast-episode** starts include the native
12
+ per-start `/noshuffle` argument, after any validated parent/cursor. Configured
13
+ account, exact item and optional parent/zero-based index stay unchanged.
14
+ Account-qualified item identifiers are retained, not stripped.
15
+ - When returning to a saved exact Local Library/Spotify item, feedback from the
16
+ same provider/account but a **different track is not success**. It cannot
17
+ trigger the old track's position seek or replace the preceding last-good item.
18
+ The player still displays the track actually reported by Loxone.
19
+ - Continuing feedback from that rejected return retains the prior bookmark.
20
+ A later explicit return attempts that retained item instead of treating the
21
+ wrong currently-playing track as a provider-only no-op. No automatic retry is
22
+ added. A matching return, a new explicit Browse selection, or independently
23
+ corroborated later different playback can update the record normally.
24
+ - After matching playback, at most **one best-effort** position attempt retains
25
+ existing bounded capability readiness, authentication, room/account/track,
26
+ lifecycle, event/fence and newer-action checks. An unsupported/failed optional
27
+ seek does not turn matching confirmed playback into source failure.
28
+ - A positively qualified current native Spotify session can still use native
29
+ empty-content resume. Unknown/history-only context return retains its existing
30
+ provider contract; exact remembered-item confirmation does not invent an item
31
+ for a context. Local Library commands, context starts, radio, Line-In,
32
+ explicit native Shuffle/Repeat setters and their capability rules are unchanged.
33
+
34
+ There is no Spotify API, compensating Shuffle/Repeat write, queue controller,
35
+ new setting, delay-only fix, cursor arithmetic change, fallback item, cross-room
36
+ policy or persistence implementation. The existing playback setup gate remains
37
+ required (see the main README).
38
+
39
+ ## Function-to-feature map
40
+
41
+ | File/symbol | Change and regression coverage |
42
+ | --- | --- |
43
+ | `pyloxone_audio/native.py:AudioServerCommands.async_play_catalogue` | Type-specific per-item flag; `tests/library/test_exact_track_start.py` checks tracks/episodes, qualified IDs, no parent and cursors 0/13/63, plus context/other-route negatives |
44
+ | `pyloxone_audio/sources.py:_source_matcher`, `async_select_catalogue` | Saved exact-item match before confirmation/optional seek; preserve ordered source/track/account restore reasons |
45
+ | `pyloxone_audio/selection.py:async_source_route` | Owning adapter may suppress current-session no-op after a rejected exact return; ordinary native session routing unchanged |
46
+ | `adapters/pyloxone/overlay/custom_components/loxone/media_player.py:LoxoneAudioZoneV2._update_audio_metadata`, `async_select_source`, `_async_play_radio` | One room-local rejected-return marker, using the existing saved session and passive qualification. Protect last-good without changing truthful metadata; release on qualified replacement/explicit success |
47
+ | `adapters/pyloxone/overlay/custom_components/loxone/audio_diagnostics.py:_safe_event` | Persisted strict schema retains the aliased Spotify flag; recovery/extra-tail/non-Spotify negatives in owning-HA tests |
48
+ | `pyloxone_audio/diagnostic_events.py:DiagnosticEmitter.route` | Recognise only the fixed per-item flag, preserving keyed account/content/parent aliases; unknown tails still refused |
49
+ | `tests/adapter/test_exact_track_return_ha.py` | Real owning HA service/coordinator/authenticated native transport, synthetic I/O: 30.021s/index13 shape, wrong feedback even with new flag and reported shuffle false/true, delayed correct feedback, account/room negatives, explicit retry/Browse, late matches and independent replacement |
50
+
51
+ Existing full-suite lifecycle/authentication/cancellation, all-room isolation,
52
+ mode, manual seek, announcement, setup/unload and no-uncertain-replay controls
53
+ remain part of acceptance. See the maintainer's `EXACT-TRACK-DELTA.json` and
54
+ `EXACT-TRACK-TEST-MIGRATIONS.json` for exact file/expectation dispositions.
55
+
56
+ ## Source evidence and limits
57
+
58
+ The pinned official Loxone 17.2.2 client, retrieved 26 October 2026 from the official
59
+ Linux release AppImage, uses `playItem -> shouldShuffleAudioItem ->
60
+ ServicePlayScheme`; individual track/episode types choose `noshuffle`, serialized
61
+ **after** `parentpath/<parent>/<zero-based-index>`. The retained bundle SHA-256 is
62
+ `76f4ff34c3156cf9109524c37640d33352968fc7dc7477af6866a9983f5abacf`.
63
+ This is command/caller parity, not proof of the physical firmware's selection
64
+ algorithm or a persistent shuffle/queue-preservation guarantee. The motivating
65
+ wrong-track observation included reported shuffle **false**, so it is not
66
+ explained simply by a shuffle-on switch.
67
+
68
+ Wrong-feedback fixtures are deliberately independent of the flag. A simulator
69
+ that returns the right item when it sees the flag cannot prove the hardware cause.
70
+ Those offline fixtures use no device credentials or household commands. Later
71
+ owner-operated one-room returns supplied matching content/position confirmation;
72
+ parent/queue, universal audible position and persistent-mode effects remain unproved.
73
+
74
+ The receiver cannot establish an external command's issuer from identical
75
+ same-item feedback. Continuing the rejected item alone does not establish a new
76
+ independent selection. Explicit Browse can deliberately choose that item; later
77
+ qualified different playback also replaces the marker. This is a bounded
78
+ interpretation of observed identity, not invented causal device evidence.
79
+
80
+ ## Review and adoption
81
+
82
+ 1. Inspect the exact delta, test-migration map and retained original failed receipts.
83
+ 2. Reproduce canonical source and exact installed-wheel suites with blocked TCP,
84
+ strict runtime/resource warning gates, case parity and import evidence.
85
+ 3. Verify the complete preview against its provenance; exercise the unchanged
86
+ builder/install/update/rollback tests. Preview material stays out of Python
87
+ wheel/sdist. Do not use older same-version artifact hashes.
88
+ 4. Preserve the completed independent exact-correction review and common adoption
89
+ evidence. Re-review changed runtime; historical mode/seek reviews alone do not
90
+ approve future changes.
91
+ 5. Keep software tests, installed-byte verification, one-room owner acceptance and
92
+ the public production gate separate. Upstream adoption/contact is not implied.
93
+
94
+ Restart persistence is a separate design and is intentionally not bundled here.
@@ -11,3 +11,7 @@ include README.md
11
11
  include SPOTIFY.md
12
12
  prune tests/release
13
13
  recursive-include tests/library *.py *.ini *.json *.txt *.md
14
+ include POSITION-RESTORE.md
15
+ include DIAGNOSTICS.md
16
+ include NATIVE-MODES.md
17
+ include EXACT-TRACK-RETURN.md
@@ -0,0 +1,96 @@
1
+ # Native Shuffle and Repeat — 0.1.1
2
+
3
+ ## What appears in Home Assistant
4
+
5
+ Open an existing Loxone room's **More info** media-player dialog. Where the
6
+ current Audio Server reports support, the normal HA controls offer **Shuffle**
7
+ and **Repeat**. Shuffle is on/off; Repeat cycles **Off → All → One → Off**.
8
+ The standard icons distinguish the reported modes. Compact Media Control/Tile
9
+ cards do not necessarily show extended controls. No custom card, helper switch,
10
+ frontend resource or Core patch is installed.
11
+
12
+ Configure **Audioserver host** and turn on the existing option
13
+ **Use Audio Server player play/pause (experimental; off by default)**
14
+ (`audioserver_playback`). This option and its default are unchanged. Metadata can
15
+ still be received with control disabled. **Audio diagnostics** is a different,
16
+ default-off feature, not a prerequisite for mode control.
17
+
18
+ These are **Loxone player controls, not Spotify controls**. Every action addresses
19
+ one exact existing native Audio Server player through the library's current-token
20
+ command client. Local Library and any other genuinely capable native source use
21
+ the same implementation. There is no Spotify Web API/Connect command, HA Spotify
22
+ entity/service, provider credential, binding or provider-derived mode state in
23
+ this feature. Existing room identities, configuration and independent bookmarks
24
+ are retained. Radio or Line-In labels do not imply support.
25
+
26
+ ## Truthful state and limits
27
+
28
+ - The exact player's native `plshuffle` and `plrepeat` feedback supplies the
29
+ displayed state, including changes made in Loxone or another controlling app.
30
+ A submitted HA request or successful socket write does not turn the indicator
31
+ on. A validated native reply acknowledges the command; this adapter waits for
32
+ the ordinary exact-player feedback to change its displayed mode.
33
+ - Missing, malformed or disconnected state is **unknown**, not a fabricated Off.
34
+ Full native player-state records are replaced rather than merging absent mode
35
+ fields from an older track/session. Announcement events are not queue state.
36
+ - Shuffle requires native bit2048, known shuffle state, current ordinary playing
37
+ media, and API >=1.6 / firmware >=16.1.09.23 on the event connection. The command
38
+ connection must independently qualify that greeting before sending an explicit
39
+ enable/disable. There is no fallback to the older no-argument shuffle action.
40
+ - Native repeat is **0 Off / 1 All / 3 One**, never2. HA has one REPEAT_SET feature
41
+ and its standard button cycles all three modes. Therefore this adapter exposes
42
+ that button only with known repeat state and **both** native permissions4096
43
+ and8192 on current playing/paused media. Partial repeat support is not advertised
44
+ as a full cycle; a direct HA setter also refuses it. The library's qualification
45
+ helper can check a specifically requested repeat mode for another typed host.
46
+ - Support follows current native data, not provider/source-name guesses. Unknown
47
+ permissions and unsupported source records hide the extended control without
48
+ changing existing Miniserver controls. Unknown or older shuffle firmware does
49
+ not disable otherwise-qualified native Repeat.
50
+ - Exactly one requested native mode command is sent. No play, source start,
51
+ queue replacement, seek, skip or automatic retry is added. In particular this
52
+ mode-control feature does **not** replace queues or implement source-return
53
+ selection. The separate exact-item return correction is described in
54
+ [EXACT-TRACK-RETURN.md](EXACT-TRACK-RETURN.md).
55
+ - A queued request cannot survive loss of its original track/account/context,
56
+ capability, connection, newest-action ownership or entity lifetime while
57
+ authentication is awaited. Existing shared serialization and clean cancellation
58
+ remain. Mode feedback does not alter artwork identity or saved source content.
59
+
60
+ ## Portable API and adapter map
61
+
62
+ `AudioRepeatMode` and `audio_mode_supported` are public HA-independent exports.
63
+ `AudioZoneMetadata.shuffle` is `bool | None`; `repeat` is
64
+ `AudioRepeatMode | None`; `shuffle_switch_supported` is connection-qualified
65
+ `bool | None`. Only greeting versions are retained, never the session credential.
66
+
67
+ `AudioServerCommands.async_set_shuffle(player_id, bool, *, is_current=...)` and
68
+ `async_set_repeat(player_id, AudioRepeatMode, *, is_current=...)` reuse current
69
+ authentication, bounded command-wide timeout, connection serialization and close.
70
+ The host must qualify the latest native snapshot and maintain a liveness callback
71
+ across awaits. The repeat API rejects bare integers/strings rather than silently
72
+ coercing an unknown enum. Setters strictly validate the corresponding native mode
73
+ reply. Neither setters nor helpers import Home Assistant or own provider APIs.
74
+
75
+ The existing AudioZoneV2 entity translates HA SHUFFLE_SET/REPEAT_SET and
76
+ `async_set_shuffle`/`async_set_repeat` into those calls. The normal event callback
77
+ updates HA attributes. The same policy is used in every mapped room, with no
78
+ per-room flags/delays. Strict optional diagnostics add only fixed aliased command
79
+ routes and typed mode fields; default-off and all storage/export bounds remain.
80
+
81
+ ## Evidence and remaining acceptance
82
+
83
+ The maintained tests use synthetic data and real owning HA services, native
84
+ RSA/AES authentication and AF_UNIX WebSocket framing with TCP blocked. The all-room
85
+ matrix includes Local Library and native Spotify without loading HA Spotify;
86
+ additional positive/negative permission controls do not claim physical radio or
87
+ Line-In firmware support. Existing mode-control tests remain separate from exact-item source-start tests.
88
+ Version-matched frontend function tests exercise HA's real control/icon/cycle
89
+ functions using emitted HA entity states, not a replacement player UI.
90
+
91
+ Independent review and common private HAOS adoption are complete. Actual mode
92
+ requests and native mode feedback were observed, but those are not proof of
93
+ physical queue behaviour or every rendered frontend control. The ordinary
94
+ install/backup/update/rollback route is in
95
+ [the preview guide](preview-build/README.md); the maintainer's exact patch and
96
+ what/why/test map are in [the review pack](adapters/pyloxone/jo-review/README.md).
@@ -1,15 +1,17 @@
1
- # Provenance and licence — concrete offline extraction
1
+ # Provenance and licence — PyLoxone-Audio 0.1.1
2
2
 
3
- This local 0.1.0 release candidate is supplied under Apache-2.0. `LICENSE` is copied verbatim from the local PyLoxone repository's Apache License, Version 2.0 file. No package has been published or registered. The library's 0.1.0 metadata and exact local host dependency are not a public index release or installed production change.
3
+ PyLoxone-Audio 0.1.1 is supplied under Apache-2.0. Published 0.1.0 remains
4
+ immutable. `LICENSE` retains PyLoxone's Apache License, Version 2.0 verbatim.
5
+ Actual public availability is established by the matching release production gate.
4
6
 
5
- The HA entry ownership, exact-player mapping, current-token callback and entity-property delegation are adapted from the PyLoxone integration maintained by JoDehli and its contributors, specifically the sealed `media_player.py`, `audio.py` and `coordinator.py` call graph. Those inspected source files contain no additional per-file copyright notices to remove or replace. This draft does not assign invented upstream copyright ownership.
7
+ The HA entry ownership, exact-player mapping, current-token callback and entity-property delegation are adapted from the PyLoxone integration maintained by JoDehli and its contributors, specifically the sealed `media_player.py`, `audio.py` and `coordinator.py` call graph. Those inspected source files contain no additional per-file copyright notices to remove or replace. This project does not assign invented upstream copyright ownership.
6
8
 
7
9
  The original native protocol source retains this attribution, which must accompany any future adaptation of its event mapping:
8
10
 
9
11
  > Event names adapted from SebastianEitzert's Apache-2.0 POC in #523, comment 6251138699. See tests/fixtures/audioserver/README.md for provenance.
10
12
 
11
- The actual parser/event mapping is now extracted into `pyloxone_audio/native.py`, retaining that attribution. Native transport/authentication, metadata coordination, source policy, radio and catalogue implementations are derived from the immutable accepted source tree identified by `baseline-verification.json` and the extraction provenance receipts. Native announcement ownership and the native read-only-bridge Spotify session selection are also extracted; HA preparation, provider session storage and UI responsibilities stay with the host. Relevant upstream attribution and Apache-2.0 terms are retained; no invented copyright assignment is made.
13
+ The actual parser/event mapping is now extracted into `pyloxone_audio/native.py`, retaining that attribution. Native transport/authentication, metadata coordination, source policy, radio and catalogue implementations are derived from the immutable accepted source tree identified by `baseline-verification.json` and the extraction provenance receipts. Native announcement ownership remains extracted; the optional HA-Spotify bridge/provider session controller was retired in0.1.1, preserving native Spotify. HA preparation and UI responsibilities stay with the host. Relevant upstream attribution and Apache-2.0 terms are retained; no invented copyright assignment is made.
12
14
 
13
- Historical fake-facade, extraction and operational evidence remains outside release assets. The concrete package exports real native classes and source-policy functions instead. Synthetic fixture provenance is preserved under `tests/library/fixtures/audioserver/README.md` outside the wheel. The older path in the attribution quoted above is historical; the current location applies to this repository and its source archive. Development-only detailed tracing and HA failure-summary publication are excluded from both library and adapter at this cutoff.
15
+ Historical fake-facade, extraction and operational evidence remains outside release assets. The concrete package exports real native classes and source-policy functions instead. Synthetic fixture provenance is preserved under `tests/library/fixtures/audioserver/README.md` outside the wheel. The older path in the attribution quoted above is historical; the current location applies to this repository and its source archive. New default-off fixed-schema audio diagnostics supersedes the blanket instrumentation exclusion only for this increment; legacy private tracing, household observer/watchdog assets and HA failure-summary publication remain excluded.
14
16
 
15
17
  Dave directed requirements, behaviour decisions and normal-use validation. Implementation and offline testing were AI-assisted through Hermes with OpenAI primary models; release-cutoff implementation uses gpt-6.1-sol and the independently recorded review uses an actual primary OpenAI invocation. AI assistance is provenance, not an invented human author, a transfer of upstream ownership, hardware certification or a guarantee of defect freedom. Independent review identity and limitations are recorded in release-level evidence, not claimed as a substitute for tests.