Answers #591's open question 2: CLI-only users get no update notice
from soundtouch-service's periodic background check. Both binaries
gain a soundtouch-cli/soundtouch-backup update-check command that
does a single, on-demand GitHub Releases check via the existing
pkg/service/updatecheck package. Running the command is itself the
opt-in, so unlike the service there's no config flag or persisted
state.
pkg/service/updatecheck.Checker was already designed decoupled from
handlers.Server/main.go specifically so other binaries could import
it directly; this is that follow-through.
Adds a third way to push a clip to a speaker, surfaced by @dagrider in
#517: POST SetAVTransportURI + Play to the speaker's UPnP MediaRenderer
control endpoint (port 8091). Unlike /speaker play_info it needs no
app_key and no DNS interception, so it works on a plain LAN; the
trade-off is it switches the speaker to the UPNP source and replaces the
current playback (no duck-and-resume).
- pkg/client: SetAVTransportURI, AVTransportPlay, PlayURLViaUPnP (+ the
:8091 control-URL derivation and SOAP plumbing), with tests.
- cmd/soundtouch-cli: `speaker url-upnp --url <url>`.
- docs: document the UPnP/AVTransport option under POST /speaker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.114.0 binaries reported version 0.0.0 in the web UI. Two root causes,
both fixed here.
1. The release build relied solely on Go's VCS stamping of
info.Main.Version and never injected a version. When v0.114.0 was
re-released via workflow_dispatch from `main` (one commit past the
tag) with a shallow checkout, no tag was reachable, so Go stamped a
v0.0.0-<ts>-<sha> pseudo-version. The asset filenames used the
validated input version, so the files were named v0.114.0 but
reported 0.0.0 at runtime.
2. The `release` and `workflow_dispatch` triggers followed two distinct
patterns. On `release` every job's checkout landed on the tagged
commit (GITHUB_SHA == tag); on `workflow_dispatch` they all built
whatever branch the run started from. So a manual dispatch built the
wrong source entirely (binaries and Docker images alike).
Changes:
- Unify both triggers on the git tag. `validate` resolves the tag once
(inputs.tag on dispatch, release.tag_name on a release event), verifies
it exists in git, and exposes it as an output. Every other job checks
out `ref: needs.validate.outputs.tag`, so the build is always the
tagged commit regardless of trigger. The dispatch path now re-releases
an existing tag (push the tag first) instead of creating one from a
branch; it fails fast if the tag is missing.
- Inject -X main.version/commit/date into the release binaries, mirroring
the Dockerfile (which has done this since #422). version/commit no
longer depend on git stamping; commit is read from the checked-out HEAD
(not github.sha, which on dispatch is the branch HEAD). Both binaries
and Docker images take the v-prefixed tag (needs.validate.outputs.tag)
so the displayed version stays "v0.114.0", matching prior releases.
- Guard updateBuildInfo() in all four cmd/*/main.go so an injected
version (version != "dev") is never clobbered by a VCS pseudo-version.
`go install …@vX.Y.Z` still resolves the tag via build info as before.
- Collapse the duplicated `if event_name == workflow_dispatch` tag
derivations and route tag/version through needs.validate.outputs.*.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the CLI-first surface for the DLNA feature
(https://github.com/gesellix/Bose-SoundTouch/discussions/213), so the
discovery/browse/play plumbing can be exercised against a real media server
and speaker without the web build loop.
- soundtouch-cli library servers: app-side SSDP sweep
(discovery.DiscoverMediaServers); --via-speaker queries the speaker's own
/listMediaServers instead, for an A/B of the two views.
- soundtouch-cli library browse --udn <id> [--object --start --count]:
dlna.Browse of a discovered server's ContentDirectory.
- soundtouch-cli library play --url <streamURL> --mode <...>: plays a track
URL on a speaker; --mode selects the playback path (local-internet-radio,
local-music, stored-music, content-item) so the best one can be found
empirically on hardware.
- pkg/client.ListMediaServers() + models.ListMediaServersResponse for the
speaker-native (Option 2) path; an empty <ListMediaServersResponse />
parses to an empty slice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the awkward top-level `tts speak --speaker-host` with a
`speaker tts-cloud` subcommand that sits alongside the existing
`speaker tts` and uses the global --host flag (--device still works as
an alternative). The two are now clearly related: `speaker tts` sends a
Google Translate URL straight to the speaker, while `speaker tts-cloud`
routes through the service for server-side synthesis (Cloud TTS) and
playback. --speaker-host is gone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds text-to-speech that synthesizes higher-quality audio (Google Cloud
TTS) and plays it on a speaker via the /speaker endpoint. Because Cloud
TTS returns audio bytes (not a fetchable URL), the service caches the
clip and hosts it at GET /media/tts/{id}, mirroring the "ding" endpoint,
then points the speaker at that local URL.
The design is a pluggable Provider interface (pkg/service/tts) wrapping
two modes:
- translate: hands the speaker the (undocumented) Google Translate URL
directly (no credentials), reusing models.BuildTranslateTTSURL.
- google-cloud: REST API key auth (no SDK/gRPC), bytes cached locally.
Surfaces:
- service: POST /mgmt/tts/speak, GET /mgmt/tts/config, GET /media/tts/{id};
configured via TTS_PROVIDER / TTS_GOOGLE_API_KEY / TTS_LANGUAGE /
TTS_VOICE / TTS_APP_KEY / TTS_VOLUME.
- CLI: `soundtouch-cli tts speak` (calls the service with mgmt Basic Auth).
- web: a "TTS" source view (like Play URL / TuneIn), proxied to the
service via /api/device-speak/{id}.
The /speaker app_key requirement and model limitations still apply; see
docs/content/docs/reference/SPEAKER-ENDPOINT.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `find` family runs the search inside the CLI, querying the radio
provider's public API directly (no speaker cloud, no soundtouch-service).
Make it the canonical path and deprecate the speaker-based search family.
- Add `find-tunein` and `find-radiobrowser` siblings; refactor the find
actions onto a shared `runFind` helper (all support `--more`).
- Rename the unreleased `search-radiobrowser` to `find-radiobrowser`.
- Deprecate `search`, `search-tunein`, `search-pandora`, `search-spotify`:
they keep working but print a stderr deprecation notice (new
`PrintDeprecation` helper) pointing at the `find*` replacement. Pandora
and Spotify have no built-in equivalent yet (they need the speaker +
account), so their notices say so.
- Docs: lead with the `find` family as recommended; mark the speaker-based
search commands deprecated; drop the misleading "service-side" wording
in favour of "built-in / queries the provider directly".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a provider-neutral station orchestration layer and expose it in the
CLI so TuneIn and Radio Browser search work consistently without
depending on the speaker's (dead) cloud search. Substance of #338.
- pkg/service/stations: new package with Search/SearchNext/Navigate/
ResolveContentItem/Play over both providers; centralises the
SourceAccount placeholder guard.
- soundtouchweb: the six TuneIn/Radio Browser handlers become thin
adapters over the new package (behaviour preserved; bmxpkg retained
for HandlePlayURL).
- bmx/radiobrowser: add offset/cursor pagination
(RadioBrowserSearchPage + RadioBrowserSearchNext) mirroring the
TuneIn opaque-cursor pattern; BmxNext only on full pages.
- marge: classifyLearnedSource gains a provider-39 (RADIO_BROWSER)
case + classifyAsRadioBrowser helper (candidate fix for #334
INVALID_SOURCE; location-substring match still to be confirmed
against a real recording).
- cli: new `station search-radiobrowser` sibling and unified
`station find --provider tunein|radiobrowser [--more]`. The existing
generic device-side `station search --source` is kept unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The speaker's BMX module calls GET on the stored preset location and
expects a BmxPlaybackResponse JSON from the AfterTouch Orion endpoint.
Storing a bare stream URL (e.g. http://davefmradio.no-ip.org:8000/stream)
causes BMX to receive raw ICY audio, which it cannot parse; playback
silently stays on the previous source and no error is surfaced.
Add --service-url / SOUNDTOUCH_SERVICE_URL to `preset set`. When set
alongside --source LOCAL_INTERNET_RADIO and a raw HTTP(S) location, the
CLI wraps the stream URL in the Orion station endpoint:
<service-url>/core02/svc-bmx-adapter-orion/prod/orion/station
?data=<base64({"name":"…","imageUrl":"…","streamUrl":"…"})>
Without --service-url the command still works but prints a clear warning
explaining why the saved preset is likely to not play, rather than saving
a silently broken location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Health check (checks_stale_internet_radio.go): detects stub INTERNET_RADIO
sources (empty credentials) left on devices initialised before the stub was
removed from the default source list. Quick-fix removes by ID; skips any
INTERNET_RADIO source that has real credentials.
Datastore: DeleteSourceByID and DeleteSourceByType (uniqueness-guarded).
API: DELETE /setup/sources/{account}/{device}/{sourceID}
CLI — two new commands:
soundtouch-cli cloud source remove --service-url ... --account ... --device ... [--id 10002 | --type INTERNET_RADIO]
Talks to AfterTouch (service side). --type resolves to canonical ID
locally; fails for unknown types.
soundtouch-cli source notify-updated --host <speaker-ip>
Talks to the speaker directly. Fetches device ID from /info, then
POSTs sourcesUpdated to :8090/notification so the speaker re-fetches
its source list immediately.
CloudCommonFlags (--service-url / AFTERTOUCH_URL) mirrors CommonFlags
(--host) for AfterTouch-facing command groups.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Discovery cycles emit one line per UPnP M-SEARCH header, one per
parsed response, and one per enrichment step — by default. A typical
service-binary cycle prints ~50–80 lines for a 3-speaker LAN. Most
operators want a startup-and-summary view; the per-packet trace is
only useful for debugging.
- New SetVerbose/IsVerbose/logVerbose helpers in pkg/discovery (atomic
bool, zero-value off).
- Chatty log.Printf calls in upnp.go and mdns.go demoted to logVerbose:
per-header dumps, per-response dumps, per-device enrichment steps,
M-SEARCH details, read-deadline / cancel-context noise.
- Kept at default level: discovery start ("Starting SSDP discovery
for…"), end ("Discovery completed. Processed N responses, found N
unique devices" + per-device summary), warnings ("Configured
interface not found", "Failed to fetch device description", …), and
the new "Rejecting non-Bose device" classifier.
- cmd/soundtouch-cli/discover devices grew a --verbose / -v flag that
flips the package toggle on; the service binary leaves it at the
zero value.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a `soundtouch-cli source tunein` subcommand that takes a TuneIn
guide ID and routes it through the right SelectContentItem shape —
`--station`, `--episode`, `--program`, or `--id` with prefix
auto-detect. The flag picks the ContentItem Type (`stationurl` for
stations/episodes, `tracklisturl` for programs) and the location
template, then enriches the now-playing metadata from TuneIn's describe
endpoint unless `--no-lookup` is set.
Program IDs (`p<N>`) are containers, not streams. The legacy OPML
`Tune.ashx?id=p<N>` returns `#STATUS: 400`, which pre-filter went out
to the speaker verbatim. Fix in three layers:
1. `parseTuneInStreamBody` filters `#`-prefixed comment lines out of
Tune.ashx responses and errors when nothing playable remains, so
a broken TuneIn reply surfaces as a real 500 instead of corrupting
the playback response.
2. `TuneInPlaybackPodcast` expands `p<N>` to its newest episode via
`api.radiotime.com/profiles/{id}/contents` (same JSON shape as
api.tunein.com; uses the radiotime mirror so all program traffic
stays on the host already in `allowedTuneInHosts`).
3. `tuneInSearchProfile` (Program search items) and
`TuneInNavigateProfile` (program detail hero) now emit
`BmxPlayback` links, so soundtouch-web renders play buttons on
program cards and on the profile hero — clicking either plays the
latest episode via the same backend expansion.
Tests pin the parser contracts (`#STATUS: 400` filter, program-contents
episode pick) and the navigate Program-only playback emission. CLI
resolver has table-driven coverage for kind selection, prefix
auto-detect, and conflicting-flag errors.
Endpoint contract + raw probe responses captured under
`_/i226/tunein-api-findings.md` and `_/i226/tunein-probe/` for future
reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `soundtouch-cli setup` subcommand group covering the full reset →
re-provision → pair lifecycle as a scriptable alternative to the web UI:
inspect, verify, plan, factory-reset, wait-ap, wifi-push, wait-online,
ssh-check, install-ca, migrate, reboot, pair (bare | full state machine)
Supporting library code lives in pkg/service/setup: factory_reset.go,
wifi_provision.go, inspect.go, init_plan.go, setup_session.go.
Confirmed against ST10 firmware 27.0.6 that bare setMargeAccount over
WebSocket — no SETUP_START/SETUP_ENTER/SETUP_LEAVE bracket — is
sufficient to pair a factory-reset speaker; the firmware materializes
SystemConfigurationDB.xml and Sources.xml itself and the pairing
survives reboot. Result and field-by-field SystemConfigurationDB
comparison documented in docs/analysis/SETUP-WEBSOCKET-EXPERIMENT.md.
Captures the device's pre-reset DELETE-to-marge plus its LAN peer
notification flow in docs/analysis/FACTORY-RESET-PROTOCOL.md.
Perf: batch GetMigrationSummary's SSH probes into one Run() call via
ssh_probe.go / ssh_probe_apply.go — was ~8 sequential dials at
500-1000 ms each on FW 27 crypto, now one round-trip. Same data shape,
same MigrationSummary fields populated.
Fixes /clockTime and /clockDisplay wire formats — firmware 27 rejects
the legacy flat XML ("Error parsing request"). ClockTimeRequest now
uses utcTime attribute; ClockDisplayRequest emits the nested
<clockConfig> envelope with timezoneInfo/timeFormat/brightnessLevel.
Removes cmd/example-init-speaker (superseded by setup pair).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the speaker-side group API surface (path 1 of the two
approaches gmuth outlined in issue #252): clients form, rename, and
dissolve stereo pairs directly on the device, and the resulting
GroupService.xml persists on disk in the same shape the device emits
over /getGroup.
What landed:
- pkg/models/group.go: Status field + IsEmpty() helper, matching the
GET /getGroup response shape (id-attr, masterDeviceId, roles,
senderIPAddress).
- pkg/client/client.go: GetGroup, AddGroup, UpdateGroup, RemoveGroup.
The endpoint name is /getGroup (not /group, despite some wiki docs)
— confirmed against a real ST-10's /supportedURLs. RemoveGroup uses
GET per the wire spec.
- cmd/soundtouch-cli/cmd_group.go + main.go: new `group` subcommand
with status / create --left --right [--name] / rename / remove,
mirroring gmuth's group.sh recipe.
WebSocket notifications:
- pkg/models/websocket.go: EventTypeGroupUpdated +
GroupUpdatedEvent + dispatch helpers. The device fans this out to
both LEFT and RIGHT speakers on every group mutation, including
empty-group teardowns; the parse test covers both shapes.
- pkg/client/websocket.go: OnGroupUpdated registration and dispatch.
- cmd/soundtouch-cli/cmd_events.go: `group` filter +
handleGroupEvent formatter.
WebSocket observability (came up while validating the above against
a real device):
- New RawMessageHandler type + OnRawMessage hook that fires for every
incoming frame before parsing, with the parse error alongside.
- New --debug flag on `events subscribe` with modes all / unknown /
errors. Raw output goes to stderr so it composes cleanly with
shell redirects.
The pkg/client refactor in this commit also adopts speaker.HTTPPort
(introduced in the previous refactor) — the unexported
defaultSoundTouchPort and three hard-coded 8090 literals are gone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✨ New Features:
- Add SelectContentItem() method for direct ContentItem selection
- Add SelectLocalInternetRadio() with full streamUrl format support
- Add SelectLocalMusic() for SoundTouch App Media Server content
- Add SelectStoredMusic() for UPnP/DLNA media server content
📻 streamUrl Format Support:
- Full implementation of wiki specification for LOCAL_INTERNET_RADIO
- Support for proxy URLs: http://contentapi.gmuth.de/station.php?name=Station&streamUrl=ActualStream
- Direct stream URL support for simple internet radio
- Complete ContentItem structure with metadata and artwork
🖥️ CLI Commands:
- Add 'source internet-radio' command with streamUrl support
- Add 'source local-music' command for local media server content
- Add 'source stored-music' command for UPnP/DLNA content
- Add 'source content' command for advanced generic selection
- All commands include comprehensive flag support and validation
🧪 Testing:
- Add 17+ comprehensive unit tests covering all scenarios
- Test streamUrl format validation and parsing
- Test error handling and parameter validation
- Test default value assignment and ContentItem construction
- All tests passing with full coverage
📚 Documentation:
- Update CLI-REFERENCE.md with new command examples
- Add complete content-selection example with working code
- Add implementation summary document
- Include API documentation for all new methods
- Add usage examples for both API and CLI
🔗 References:
Implements features from SoundTouch WebServices API Wiki:
- https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API#select-local_internet_radio---streamurl-format
- https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API#select-local_music🎯 Benefits:
- Complete API coverage for advanced content selection
- Backward compatible with existing code
- Flexible design with both convenience and power-user methods
- Production-ready with comprehensive testing and documentation
Co-authored-by: SoundTouch WebServices API Wiki <https://github.com/thlucas1/homeassistantcomponent_soundtouchplus>
🔥 NEW ENDPOINTS IMPLEMENTED:
📊 /introspect endpoint:
- Get detailed music service state and capabilities data
- Support for SPOTIFY, PANDORA, TUNEIN, AMAZON, DEEZER services
- Service state tracking (Active, Inactive, InactiveUnselected)
- Playback capabilities (skip, seek, resume, data collection)
- Authentication token status and user account information
- Subscription type and content history metadata
📚 /recents endpoint:
- Retrieve recently played content history
- Support for all music sources (Spotify, Local, TuneIn, Pandora, etc.)
- Rich filtering by source type and content type
- Content classification (tracks, stations, playlists, albums)
- Presetable item identification and artwork metadata
- Timestamp tracking with UTC time support
⚡ CLIENT API:
- client.Introspect(source, sourceAccount) method
- client.IntrospectSpotify(sourceAccount) convenience method
- client.GetRecents() method with comprehensive filtering
- Complete error handling and validation
- Rich helper methods for content analysis
🖥️ CLI COMMANDS:
- soundtouch-cli source introspect --source <SERVICE>
- soundtouch-cli source introspect-spotify
- soundtouch-cli source introspect-all (bulk introspect)
- soundtouch-cli recents list [--detailed] [--limit N]
- soundtouch-cli recents filter --source <SRC> --type <TYPE>
- soundtouch-cli recents latest (most recent item)
- soundtouch-cli recents stats (detailed analytics)
📦 MODELS & FEATURES:
- IntrospectRequest/Response with service-specific handling
- RecentsResponse with RecentsResponseItem for individual items
- Rich filtering: GetSpotifyItems(), GetTracks(), GetPresetableItems()
- Content type detection: IsTrack(), IsStation(), IsPlaylist()
- Source classification: IsStreamingContent(), IsLocalContent()
- Full XML marshalling/unmarshalling with proper attribute handling
🧪 COMPREHENSIVE TESTING:
- Unit tests for models with XML parsing validation
- Integration tests for real device communication
- CLI command tests with mock server responses
- Error condition testing and edge case handling
- Performance tests and timeout validation
📖 DOCUMENTATION & EXAMPLES:
- Updated API endpoints overview marking endpoints as implemented
- Comprehensive CLI reference with usage examples
- Removed endpoints from unimplemented list
- Updated wiki implementation plan status
- Complete example applications with README guides
- Real-world usage patterns and best practices
✨ KEY FEATURES:
- Service health monitoring and diagnostics
- Recently played content discovery and analysis
- Preset candidate identification
- Content statistics and usage analytics
- Time-based filtering and relative timestamps
- Rich emoji-based CLI output formatting
- Cross-service compatibility and error handling
This implements two critical missing endpoints from the SoundTouch API,
providing essential functionality for music service management and
recently played content analysis with full programmatic and CLI access.
Add WebSocket event monitoring functionality to soundtouch-cli:
• New 'events subscribe' command for real-time device monitoring
• Support for all 8 event types: nowPlaying, volume, connection, preset, zone, bass, sdkInfo, userActivity
• Event filtering with --filter flag (comma-separated list)
• Duration limits with --duration flag
• Reconnection control with --no-reconnect flag
• Verbose logging with --verbose flag
• Comprehensive test coverage with 349+ test cases
• Full documentation updates in CLI-REFERENCE.md and websocket-events.md
Usage examples:
- soundtouch-cli --host 192.168.1.100 events subscribe
- soundtouch-cli --host 192.168.1.100 events subscribe --filter volume,nowPlaying
- soundtouch-cli --host 192.168.1.100 events subscribe --duration 5m --verbose
Resolves README discrepancy - the documented command now works as expected.
All golangci-lint issues resolved, maintains code quality standards.
- Add PlayInfo model for TTS and URL content playback requests
- Add SpeakerResponse model for endpoint responses
- Implement client methods: PlayTTS, PlayURL, PlayCustom, PlayNotificationBeep
- Add comprehensive CLI commands for speaker functionality:
- speaker tts: Text-to-Speech with Google TTS and language support
- speaker url: Audio content playback from HTTP/HTTPS URLs
- speaker beep: Simple notification beep sound
- speaker help: Detailed functionality documentation
- Support for volume control (0-100 or current volume)
- Multi-language TTS support (EN, DE, ES, FR, IT, NL, PT, RU, ZH, JA, etc.)
- Custom metadata support for NowPlaying display
- Comprehensive validation and error handling
- Full test suite with XML marshaling/unmarshaling tests
- Complete documentation with API reference and usage examples
- Compatible with ST-10 (Series III) and other supported SoundTouch devices
The /speaker endpoint enables notification and audio content playback,
automatically managing volume restoration and content interruption.
Perfect for home automation, alerts, and custom audio notifications.
✨ New Features:
- Implement missing /supportedURLs endpoint with full XML parsing
- Add comprehensive endpoint-to-feature mapping system (15+ features, 9 categories)
- Create device capability analysis with personalized recommendations
- Add intelligent device classification (Premium, Standard, Basic, Essential, Limited)
🔧 CLI Enhancements:
- Add 'supported-urls' command with --features and --verbose flags
- Add 'analyze' command for comprehensive device capability analysis
- Add 'station list' command for saved station management
- Add 'source availability' and 'source compare' commands
- Enhanced service availability checking across all commands
📚 Models & API:
- New SupportedURLsResponse model with rich helper methods
- Enhanced ServiceAvailability model with validation utilities
- New EndpointFeature mapping system with CLI command references
- Feature completeness scoring and partial implementation detection
🧪 Testing:
- 35+ new test cases covering all functionality
- Comprehensive feature mapping validation tests
- Service availability integration tests with real device scenarios
- Mock server tests for error handling and edge cases
📖 Documentation:
- New FEATURE-MAPPING-GUIDE.md with comprehensive usage examples
- Updated API documentation with correct implementation status
- CLI command reference organized by feature category
- Device troubleshooting guide with capability checking
🎯 Key Capabilities:
- Device feature coverage scoring (0-100%)
- Essential vs optional feature classification
- Personalized CLI command recommendations
- Missing capability detection with usage impact analysis
- Smart device type classification based on supported endpoints
This resolves the documentation inconsistency where /supportedURLs was marked as
implemented but was actually missing from the client. The new implementation goes
far beyond basic endpoint listing to provide intelligent device capability analysis
and personalized usage recommendations.
- Add missing CLI commands for navigation and station management functionality
- Implement browse commands (content, menu, container, tunein, pandora, stored-music)
- Implement station commands (search, add, remove) for all sources (TuneIn, Pandora, Spotify)
- Create comprehensive examples for preset management and navigation/station demo
- Update all documentation to properly credit SoundTouch Plus Wiki as endpoint source
- Correct attribution from 'reverse engineering' to community-documented endpoints
- Add Related Projects section acknowledging SoundTouch Plus and SoundCork
- Update API coverage documentation to reflect 100% functional implementation
- Resolve GitHub issue #14 with complete preset management and direct content playback
Resolves: #14
- Add StorePreset, StoreCurrentAsPreset, and RemovePreset methods to client
- Create comprehensive preset management CLI with subcommands:
* preset store-current --slot N (store currently playing content)
* preset store --slot N --source X --location Y (store specific content)
* preset remove --slot N (remove preset)
* preset select --slot N (select/play preset)
* preset list (list all presets)
- Fix WebSocket event handling for preset updates:
* Correct event type from 'presetUpdated' to 'presetsUpdated'
* Update event structure to handle complete preset list
* Improve WebSocket demo display for preset events
- Add comprehensive test coverage for all new client methods
- Fix mock server URL mismatch in tests (/now_playing vs /nowPlaying)
- Add proper input validation and error handling
- Support all content sources: SPOTIFY, TUNEIN, LOCAL_INTERNET_RADIO, etc.
Successfully tested with real SoundTouch device:
- Storing Spotify content as presets ✅
- Removing presets ✅
- Real-time WebSocket events ✅
- CLI usability and error handling ✅Resolves#14 - Complete /storePreset implementation
- Add automatic display of ContentItem location (URI/ID) for all sources with location data
- Add --verbose flag to 'play now' command for detailed content information
- Show location details by default for any source (SPOTIFY, TUNEIN, PANDORA, STORED_MUSIC, etc.)
- Add comprehensive test coverage for content details display logic
- Add preset-store.md documentation for future /storePreset implementation
- Update documentation to reflect universal location support
This enables users to easily capture location URIs needed for the planned /storePreset feature:
- Spotify: spotify:track:123456789
- TUNEIN: /v1/playback/station/s33828
- Internet Radio: https://stream.example.com/radio
- NAS Music: 6_a2874b5d_4f83d999
- Pandora: 126740707481236361
Resolves#14 preparation work
Major Features:
• Implement complete /requestToken API endpoint for bearer token generation
• Fix clock time parsing and display with comprehensive time information
• Add comprehensive API documentation for 103 discovered endpoints
/requestToken Implementation:
• Add BearerToken model with full XML marshaling support
• Add RequestToken() client method with proper error handling
• Add 'soundtouch-cli token request' CLI command with security features
• Token validation, formatting, and secure display (truncated for security)
• Comprehensive unit tests and integration tests
• Support for Authorization header formatting and raw token extraction
Clock Time Fixes:
• Fix ClockTime model to match actual device XML response structure
• Add LocalTime component for nested time details
• Support for utcTime, timeFormat, brightness, clockError attributes
• Enhanced CLI display with comprehensive time information
• Fixed month conversion (device uses 0-11, Go uses 1-12)
Documentation Enhancements:
• Add comprehensive /supportedURLs endpoint analysis (103 endpoints discovered)
• Create detailed unimplemented endpoints documentation with examples
• Update API coverage from 34% implemented to full endpoint catalog
• Add SoundTouch End of Life notice with May 6, 2026 details
• Enhanced endpoint descriptions with real device response examples
Security:
• All tests use generic token examples (no real tokens exposed)
• Integration tests validate token properties without exposing values
• Secure token display with truncation in CLI and string representations
• Environment variable based testing for real devices
Testing:
• 15+ new test functions with comprehensive coverage
• Real device validation on 192.168.178.28 and 192.168.178.35
• Mock server tests and XML marshaling validation
• Integration tests with SOUNDTOUCH_TEST_HOST environment variable
CLI Enhancements:
• Enhanced clock time display with local time details and device settings
• New token management commands with usage instructions
• Improved error handling and user-friendly output formatting
- Add recursive sortCommands function to sort commands and subcommands alphabetically
- Add sortFlags function to sort flags alphabetically by name
- Add getFlagName helper to extract flag names from different flag types
- Sort both top-level commands and all nested subcommands recursively
- Sort command-specific flags while preserving auto-generated help flags
- Improve CLI usability by making commands and options easier to find
Commands and subcommands are now displayed in alphabetical order in help output.
All user-defined flags are sorted alphabetically within each command.
- Use package-level variables instead of mixed return/ignore pattern
- Call updateBuildInfo() once at startup instead of multiple function calls
- Cleaner, more consistent design with single responsibility
- Eliminates confusing 'version, _, _' usage pattern
Thanks for the excellent code review feedback!
Remove unnecessary truncation of Git commit hash from vcs.revision.
The full hash provides better traceability and eliminates arbitrary
magic numbers in the code.
Simpler, cleaner, and more robust approach.
- Use debug.ReadBuildInfo() for version information (Go 1.18+ best practice)
- Extract version from module info and VCS settings (vcs.revision, vcs.time)
- Remove complex ldflags setup from Makefile and GitHub workflows
- Simplify build process while maintaining all version information
- Cleaner approach recommended by Go community
Thanks to Gopher Slack feedback for this improvement!
Completes the implementation of all official Bose SoundTouch Web API v1.0
endpoints, achieving 100% official API coverage.
## New Features
### DSP Audio Controls (/audiodspcontrols)
- GetAudioDSPControls() - Get current DSP settings and supported audio modes
- SetAudioDSPControls() - Set audio mode and video sync delay
- SetAudioMode() - Set audio mode only (NORMAL, DIALOG, MUSIC, MOVIE, etc.)
- SetVideoSyncAudioDelay() - Set video sync delay only
### Advanced Tone Controls (/audioproducttonecontrols)
- GetAudioProductToneControls() - Get advanced bass/treble settings with ranges
- SetAudioProductToneControls() - Set both bass and treble
- SetAdvancedBass() - Set advanced bass level only
- SetAdvancedTreble() - Set advanced treble level only
### Speaker Level Controls (/audioproductlevelcontrols)
- GetAudioProductLevelControls() - Get front-center and rear-surround levels
- SetAudioProductLevelControls() - Set both speaker levels
- SetFrontCenterSpeakerLevel() - Set front-center speaker level only
- SetRearSurroundSpeakersLevel() - Set rear-surround speakers level only
## Implementation Details
### Models & Validation
- Complete XML marshaling/unmarshaling with proper struct separation
- Comprehensive input validation with device capability checking
- Support for device-specific ranges and step values
- Proper error handling and constraint validation
### CLI Integration
- Full CLI command tree: audio -> {dsp,tone,level} -> {get,set,specific}
- Rich help text with device-specific guidance
- Flexible parameter handling (individual or combined operations)
- Professional usage examples and CLI command demonstrations
### Testing Coverage
- 748+ lines of comprehensive model tests
- 786+ lines of client integration tests
- XML marshaling/unmarshaling validation
- Error handling and edge case coverage
- Network error simulation and validation testing
## Device Compatibility
### Consumer Devices (SoundTouch 10, 20, 30)
- ✅ Basic controls (bass, volume, balance)
- ❌ Advanced audio controls (professional feature)
### Professional/High-end Devices
- ✅ All basic controls
- ✅ DSP audio modes and video sync
- ✅ Advanced bass/treble controls
- ✅ Speaker level controls (surround systems)
## Documentation & Examples
### Updated Coverage Documentation
- README.md: Updated to 100% complete (19/19 endpoints)
- API-Endpoints-Overview.md: Complete coverage analysis
- API-COVERAGE-ANALYSIS.md: Achievement of full API implementation
### Comprehensive Examples
- advanced-audio-controls.go: Complete usage demonstration
- CLI command examples and device compatibility guide
- Error handling and validation examples
## Final API Status
- ✅ **19/19 Official Endpoints Implemented** (100%)
- ✅ **18/19 Functional on Real Devices** (95%)
- ❌ **1 Endpoint Non-functional** (/trackInfo times out on hardware)
- 🔍 **5 Extended Features** (beyond official API v1.0)
This completes the most comprehensive Bose SoundTouch API implementation
available, covering all documented endpoints plus extended functionality.
- Specify that '/now_playing' endpoint is the API alternative
- Clarify that CLI 'now' command (playback status) is the CLI alternative
- Distinguish between the two 'now' CLI commands (playback vs clock)
- Update warning messages to be more specific about alternatives
Based on real device testing, the /trackInfo endpoint returns
'AllegroWebserver timeout' errors despite being documented in the
official Bose SoundTouch Web API v1.0 specification.
## Changes
- Updated API coverage from 89% to 84% (16/19 functional endpoints)
- Marked /trackInfo as ❌ Non-functional in all documentation
- Added warning comments to GetTrackInfo() method
- Updated CLI command with warning message
- Recommend using /now_playing instead for track information
## Real Device Evidence
- Device: SoundTouch at 192.168.178.28:8090
- Error: 'AllegroWebserver timeout: /trackInfo'
- Status: Endpoint documented but not working on hardware
This reflects the reality that some officially documented endpoints
may not function properly on actual devices, emphasizing the importance
of real hardware testing in API implementation.
Implements the remaining zone slave management endpoints from the official
Bose SoundTouch Web API v1.0 specification, bringing API coverage to 89%.
## New Features
### Client Methods
- AddZoneSlave(masterID, slaveID, slaveIP) - Add individual device to zone
- AddZoneSlaveByDeviceID(masterID, slaveID) - Add device by ID only
- RemoveZoneSlave(masterID, slaveID, slaveIP) - Remove individual device
- RemoveZoneSlaveByDeviceID(masterID, slaveID) - Remove device by ID only
### Models
- ZoneSlaveRequest - Request structure for slave operations
- ZoneSlaveEntry - Individual slave entry with IP address support
- Complete XML marshaling/unmarshaling with proper omitempty handling
- Comprehensive validation and error handling
### CLI Commands
- zone add-slave --master ID --slave ID [--slave-ip IP]
- zone remove-slave --master ID --slave ID [--slave-ip IP]
## Implementation Details
- Follows official API specification exactly (POST /addZoneSlave, /removeZoneSlave)
- Supports both device ID + IP and device ID only operations
- Comprehensive input validation (IP addresses, device ID conflicts)
- Proper XML formatting with omitempty for optional IP addresses
- Extensive test coverage (580+ lines of tests)
- Integration with existing high-level zone management API
## Testing
- 200+ new test cases covering all functionality
- Complete model validation and XML marshaling tests
- HTTP client integration tests with mock servers
- Error handling and edge case coverage
- Network error simulation tests
## Documentation Updates
- Updated API coverage from 84% to 89% (17/19 endpoints)
- Comprehensive API coverage analysis document
- Updated README.md with new endpoint status
- Added practical usage examples
- CLI help documentation
## Compatibility
- Maintains full backward compatibility
- Complements existing high-level zone API
- Users can choose between low-level official API or enhanced high-level API
- No breaking changes to existing functionality
This implementation provides both the exact official API endpoints and
enhanced high-level zone management, giving users maximum flexibility
for zone operations while maintaining full API compliance.
- Force IPv4-only mDNS queries with DisableIPv6=true to avoid routing issues
- Add automatic IPv4 interface selection for better compatibility
- Filter mDNS results to only include SoundTouch devices
- Clean up device names by unescaping mDNS characters
- Fix timeout flag handling to respect DISCOVERY_TIMEOUT from .env file
- Only override discovery timeout when --timeout flag is explicitly provided
- Add file operations safety guidelines to docs/CLAUDE.md
- Remove duplicate timeout flags from discover command, use global flags
Fixes IPv6 'no route to host' errors that prevented mDNS discovery.
Now discovers same devices as native dns-sd and dig tools.
- Replace hardcoded version with build-time injected variables
- Add version, commit, and date variables to main.go with default values
- Update Makefile ldflags to use consistent variable names
- Add detailed 'version' subcommand showing build info, Go version, and platform
- Maintain compatibility with existing release workflow
- Support both --version flag (simple) and version subcommand (detailed)
- Move --host, --port, and --timeout from individual commands to global app level
- Enables cleaner syntax: 'soundtouch-cli --host 192.168.1.10 volume get'
- Consistent with Docker, kubectl, and other CLI tools that use global connection flags
- Environment variables (SOUNDTOUCH_HOST, SOUNDTOUCH_PORT) work seamlessly
- Remove repetitive CommonFlags from all individual commands
- Maintains backward compatibility - all functionality works exactly the same
- Discovery commands ignore host flag when not needed
BREAKING: CLI syntax improved from 'volume --host IP get' to '--host IP volume get'
- Added urfave/cli/v2 dependency for better CLI structure
- Created modular command structure with separate files:
- common.go: Shared utilities and client setup
- cmd_discover.go: Device discovery commands
- cmd_info.go: Device information commands
- cmd_volume.go: Volume control commands
- cmd_playback.go: Playback control commands
- cmd_source.go: Source selection commands
- Replaced giant main() function (complexity 149) with organized subcommands
- Added proper flag handling and validation
- Improved help text and user experience
WIP: Some issues remain (flag conflicts, missing commands)
Next: Complete remaining commands and fix conflicts
- Fix whitespace issues in test files (bass_test.go, client_test.go, source_selection_test.go)
- Fix whitespace issues in discovery/mdns.go
- Fix whitespace issues in config/config_test.go
- Fix whitespace issues in soundtouch-cli main.go ranges and loops
- Fix whitespace issues in models/websocket_test.go
Progress: Reduced wsl_v5 issues from 50 to 45 (10% improvement)
Total remaining: 79 issues (down from 84)
- Fix all errcheck issues by properly checking error return values
- Fix gocritic exitAfterDefer issues by replacing log.Fatalf with return statements
- Fix rangeValCopy issues by using index-based iteration for large structs
- Add missing package comments for all packages
- Fix unused parameter issues by renaming to underscore
- Fix empty block issues by adding explicit error handling
- Add documentation for exported methods and constants
- Fix shadow variable issues
- Replace deprecated strings.Title with manual implementation
- Fix defer function error handling
Reduced lint issues from 108 to 84 (22% improvement)
All critical error handling and code quality issues resolved
- Fix bodyclose issues by properly closing WebSocket response body
- Fix errcheck issues by checking errors on resp.Body.Close(), conn.Close(), etc.
- Fix errorlint issue by using errors.As() instead of type assertion
- Fix nilerr issue by adding proper logging for UPnP discovery failures
- Fix gocritic issues:
- Convert if-else chains to switch statements
- Fix parameter type combining (paramTypeCombine)
- Fix range value copying (rangeValCopy)
- Fix exitAfterDefer by calling cancel() before log.Fatalf()
- Add package comments to fix revive package-comments issues
- Rename ClientConfig to Config to avoid type name stuttering
- Add missing exported constant comments
- Fix unused parameter issues by renaming to _
- Fix empty block issues
- Add t.Helper() to test helper functions
- Update User-Agent and fix GetNetworkSummary behavior to match test expectations
Reduces linting issues from 151 to 108 (28% improvement).
All tests now pass.
Major additions:
- Fixed WebSocketMessage XMLName struct tag issue
- Implemented remaining official API endpoints:
* POST /name (SetName) - Set device name
* GET /bassCapabilities (GetBassCapabilities) - Check bass support
* GET /trackInfo (GetTrackInfo) - Track information (duplicate of now_playing)
New features:
- BassCapabilities model with validation and helper methods
- SetName method for device naming
- GetTrackInfo method for track information
- Full CLI support for all new endpoints
- Comprehensive test coverage (503 lines of tests)
Testing results:
- All unit tests pass ✅
- bassCapabilities works on SoundTouch 10 ✅
- trackInfo may not be supported on all models (timeout on SoundTouch 10)
- SetName not tested on real hardware (to avoid changing device name)
API Coverage: Now 100% of official Bose SoundTouch Web API v1.0
- 19/19 endpoints implemented
- All documented features supported
- Additional undocumented endpoints working (clock, network, balance)
- Implement GET /getZone and POST /setZone endpoints
- Add complete zone models with validation and error handling
- Create zone builder pattern for fluent API construction
- Add all zone operations: create, add, remove, dissolve
- Implement zone status queries and member management
- Add comprehensive CLI support for all zone commands
- Create 100+ tests covering all zone functionality
- Add WebSocket zone event handling and monitoring
- Include extensive documentation and examples
- Update project completion to 90% (18/20 endpoints)
BREAKING: None - all additions are backward compatible
NEW ENDPOINTS: /getZone (GET), /setZone (POST)
NEW CLI COMMANDS: -zone, -zone-status, -zone-members, -create-zone,
-add-to-zone, -remove-from-zone, -dissolve-zone