The DLNA UDN from discovery carries a "uuid:" prefix (e.g.
uuid:fa095ecc-...), but a SoundTouch STORED_MUSIC account is the bare UUID
plus /0 (the speaker's /sources reports the bare form). The mismatch made
the player Library tab show an "Add" button for an already-registered
server, and an Add via the UI would have registered a wrong "uuid:.../0"
account. Normalize (strip "uuid:") when mapping discovery results to the DTO
and when building the account in HandleAddLibraryServer, so the LAN list and
the registered list agree and Add builds the correct account. Verified live:
discover now returns the bare UDN, matching /sources.
Also populate the previously-unset MediaServer.Address from the
ContentDirectory control URL host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for browsing DLNA media servers and playing tracks on a
SoundTouch speaker (https://github.com/gesellix/Bose-SoundTouch/discussions/213).
- pkg/discovery/ssdp.go: a target-agnostic UPnP SSDP core. SearchSSDP sweeps
multiple targets (a typed device URN plus ssdp:all, since some servers only
answer one), fans out across all routable IPv4 interfaces, and sends each
batch in two rounds spaced 80ms apart so slower NAS/router boxes that drop
back-to-back bursts still answer. FetchDescription parses a UPnP device
description into a generic device tree with FindService/FirstIcon that
recurse through sub-devices. The XML parse is a pure function for offline
unit testing.
- pkg/discovery/mediaserver.go: DiscoverMediaServers rides the core, keeps
only devices exposing a ContentDirectory service, and dedupes by UDN. The
description->MediaServer mapping is a pure, tested function.
- pkg/dlna: a ContentDirectory browse client (Browse + DIDL-Lite parse +
IsAudioItem), consuming discovery.MediaServer. Kept separate from discovery,
mirroring how pkg/client is separate from pkg/discovery. Track metadata maps
upnp:artist / upnp:album; the audio filter accepts audio/* MIME or an
audioItem/musicTrack class.
Existing SoundTouch speaker discovery (pkg/discovery/upnp.go) is untouched;
migrating it onto the shared core is a later, de-risked step. Tests cover the
description/DIDL parsers and run the browse client against an in-process
ContentDirectory server; the parse was checked against real minidlna and
FRITZ!Box output.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a speaker resolves the firmware-hardcoded content.api.bose.io through
the operator's own DNS instead of AfterTouch, TuneIn/BMX content requests
escape AfterTouch and fail (CURL 60, or a dead-cloud 404), so the speaker
reports INVALID_SOURCE. The existing dns_sanity check only probes AfterTouch's
own answering side over loopback, so it passes even when no speaker uses
AfterTouch as its resolver. This adds a speaker-side, on-demand check.
dns_speaker_usage:
- pkg/discovery/dns.go tracks distinct non-loopback clients that query an
intercepted Bose hostname (interceptClients set, populated in recordQuery,
exposed via InterceptClientIPs()). Loopback is excluded so dns_sanity's own
probes don't register.
- The check lists each unconfirmed speaker as an info finding with a "Test DNS
path" quick-fix. It never emits a standing warning, so it does not
false-positive after a restart (the querier set is in-memory and starts empty).
Active probe (the "Test DNS path" quick-fix; also POST /setup/health/dns-path-probe):
- Sends a /speaker notification carrying a per-probe nonce as the app_key. To
accept it the speaker must resolve audionotification.api.bosecm.com
(intercepted) and call back GET /v1/auth with that nonce; the callback
arriving is direct proof the speaker resolves Bose hosts through AfterTouch.
- HandleSpeakerAuth returns 403 for a matching nonce so the speaker refuses the
notification (silent, no audio, confirmed on hardware); any other key still
gets 200 so real TTS is untouched. Reuses resolveTTSHost for SSRF-safe
targeting; the nonce is never logged. Registered without refresh so the probe
result stays visible in the Health tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- stockholm/static.go: wrap deferred root.Close() in func(){}() to
silence errcheck; change 'rel = rel + ...' to 'rel += ...' (gocritic).
- Remove sanitizeErr from four logutil files where no call site exists
(cmd/soundtouch-cli, cmd/websocket-demo, pkg/discovery, pkg/service/setup).
The log-injection fixes in those packages used sanitizeLog on string
arguments rather than sanitizeErr on error values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes CodeQL go/log-injection alerts in the final batch of packages.
New logutil.go helpers: pkg/client, pkg/testutils/amazon,
pkg/testutils/spotify, cmd/soundtouch-service, cmd/soundtouch-web,
cmd/dummy-speaker, cmd/mdns-scanner.
pkg/discovery/logger.go: added sanitizeLog and a nil-safe
remoteAddrString helper to the existing file (alongside logVerbose).
Call sites wrapped across 11 files — device IDs, source types,
hostnames, IPs, interface names, URLs, service names, HTTP method/form
values, WebSocket URLs and payloads, TLS SNI names, remote addresses.
No behaviour change. golangci-lint and make check pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The speaker firmware constructs the OAuth host by appending "oauth" to
the first label of the configured streaming hostname (aftertouch.lan
→ aftertouchoauth.lan, used by both Spotify and Amazon Music token
refresh). AfterTouch's DNS server previously only hijacked the
hardcoded list of Bose hostnames, so operators self-hosting at a
custom hostname had to add the OAuth alias themselves — and the
amazon-music-oauth.md / spotify-overview.md docs incorrectly
claimed the DNS server handled it automatically.
ofthesun9 (#337) caught this via the worst variant: IP-based
serverURL (192.168.0.30 → 192oauth.168.0.30), which is a malformed
hostname no DNS resolver can answer for. There is no clean DNS
workaround for the IP case — the operator must use a hostname.
Three changes:
- pkg/discovery/dns.go DeriveOAuthHostnames parses the configured
serverURL, derives <first-label>oauth.<rest> when the host is a
hostname (not IP), and adds it to the DNSDiscovery hijack list. IP
serverURLs deliberately yield no derivation — the malformed name
isn't worth handling and the new health check surfaces the trap.
- New checks_oauth_target health check fires a Warning when serverURL
is an IP literal, with a concrete example of the malformed name
(`192oauth.168.0.30`) and a ManualCommand pointing at the switch.
- amazon-music-oauth.md and spotify-overview.md rewritten: drop the
false "automatic" claim, document the three resolution paths
(AfterTouch DNS + speaker resolves via it / external LAN DNS /
per-speaker /etc/hosts), and explicitly flag IP-based --server-url
as incompatible with OAuth on either provider.
Tests cover the derivation matrix (hostname / IPv4 / IPv6 / single
label / empty / garbage URL), shouldIntercept's new behaviour
(derived host hit, base host not auto-hijacked, case-insensitive),
the health check's four states, and the malformed-host helper.
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>
Four small, independent improvements bundled into one cut:
1. Restrict device discovery to SoundTouch-family services (#269/#359).
- mDNS now queries all three SoundTouch service-type variants in
parallel (_soundtouch._tcp, _bose-soundtouch._tcp, _soundtouchstick._tcp)
and deduplicates results by host:port. mDNS has no native wildcard
for service types, so we fan out one query per variant.
- UPnP/SSDP M-SEARCH receives a manufacturer/modelName check after
fetching the device description: devices whose manufacturer doesn't
contain "bose" AND whose model doesn't contain "soundtouch" are
rejected. Closes the loop on NorbertBauer's diagnostic bundle that
showed a Dreambox dm920 and Onkyo HT-R695 living under the default
account because they answered our generic MediaRenderer:1 probe.
2. New health check: default-account-contains-non-Bose-devices (#269).
Walks devices keyed under data/accounts/default/devices/, flags any
whose ProductCode/Name doesn't look SoundTouch, and offers an Evict
QuickFix. Bose devices still in default (legitimate pre-pair) are
intentionally ignored — that's the consistency check's domain.
3. Clipboard fallback for Copy buttons (#355). The two health-tab Copy
buttons used navigator.clipboard.writeText, which requires a secure
context. Over plain HTTP at a LAN IP the browser blocks it silently
and the button shows "Copy failed". New copyTextToClipboard helper
tries the modern API first, falls back to document.execCommand("copy")
via an off-screen textarea.
4. Web UI static-asset cache-busting (#345). dekiesel needed Ctrl+F5 to
see the v0.89 Download button after upgrade. The root HTML now
carries a ?v=<hash> query string on /web/js/script.js and
/web/css/style.css references. Hash is sha256 over the embedded asset
bodies, truncated to 12 hex chars — stable per binary, changes when
the assets change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Queries this service's own DNS server for every intercepted
Bose hostname (api.bose.com, content.api.bose.io, etc.) and
verifies the answer is the configured service IP. Catches:
- DNS subsystem disabled or unbound (speakers using us as
their resolver get NXDOMAIN).
- DNS running but answers point at a stale IP (operator
changed the LAN address without restarting).
- Subset of intercepts silently failing — emits the failing
hostname list explicitly so it's obvious which patterns are
falling through shouldIntercept.
For the mismatch case the finding includes a copyable
`nslookup … <our-dns-bind>` so operators can verify the same
behaviour from the speaker's network.
To avoid duplicating the intercept list, exports it as
`discovery.InterceptedBoseHosts` instead — same string slice
that DNSDiscovery.shouldIntercept walks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the .md/.txt sweep across all tracked _test.go, testdata XML,
and .http integration files. Test files are self-contained (producer
+ assertion in the same file), so the matched-pair swap stays green
under `go test ./...`.
Mapping applied:
192.168.178.[0-9]+ → 192.0.2.[same]
192.168.1.[0-9]+ → 192.0.2.[same]
Sound Machinechen → Living Room SoundTouch
A Sound Machine → Kitchen SoundTouch
A81B6A536A98 + case/separator variants → AABBCCDDEEFF (etc.)
A81B6A849D99 → AABBCCDDEE01
A81B6A849D88 → AABBCCDDEE03
A81B6A536A09 → AABBCCDDEE04
884AEAEEBD27 → AABBCCDDEE02
3230304 → 1000001
9569497 → 1000002
Two semantic fixes alongside the bulk swap:
- pkg/service/zeroconf/zeroconf_test.go: the "private 192" and
"strips query" cases pin acceptance of RFC-1918 192.168/16. They
must use a real 192.168 value; doc-range IPs would (correctly) be
rejected by validateZcBaseURL. Switched to 192.168.10.10 — generic
enough not to match any home LAN default, real enough for the
validator. Added a comment explaining why this single test still
carries a 192.168 literal.
- pkg/service/setup/setup_test.go: TestTestDNSRedirection mocks the
device's `od -An -tu1` byte output, which is space-separated
octets ("192 168 1 100"). My sed only matched the dot-separated
form, so the mock was returning the old IP while the test
assertions had moved to the doc range. Updated to " 192 0 2 100".
go build ./... clean. go test ./... clean (only TestDocsConsistency
remains failing, which is a pre-existing/untracked-file issue).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a multi-homed host the discovery layer used to walk net.Interfaces()
and pick the first non-loopback IPv4 NIC, while UPnP/SSDP bound a
wildcard UDP socket and let the kernel route the multicast send. That
meant --bind on soundtouch-web only moved the HTTP listener; the
discovery still went out whatever interface the kernel preferred (often
the wrong one on hosts where the speakers sit behind a secondary NIC).
Introduce a separate DiscoveryInterface knob:
* pkg/config: DiscoveryInterface field + DISCOVERY_INTERFACE env var.
* pkg/discovery/mdns: NewMDNSDiscoveryServiceWithInterface; the
interface resolver now honours an explicit name and validates it
has a usable IPv4 address before handing it to hashicorp/mdns.
* pkg/discovery/upnp: when an interface is configured, bind the UDP
socket's source IP to the NIC's IPv4 and call
ipv4.PacketConn.SetMulticastInterface so M-SEARCH leaves the right
NIC. Without an interface, behaviour is unchanged.
* cmd/soundtouch-web: new --interface flag (DISCOVERY_INTERFACE env)
plumbed into the config before the discovery service is built.
go.mod/go.sum reflect promoting golang.org/x/net from indirect to a
direct dependency (now imported for ipv4.PacketConn).
Refs #264.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Endpoints:
- POST /streaming/music/musicprovider/{id}/trial/is_eligible (reuses
is_eligible handler)
- POST /bmx/tunein/v1/favorite/{stationID} with datastore persistence
(SaveTuneInFavorite)
- DELETE /bmx/tunein/v1/favorite/{stationID} (DeleteTuneInFavorite)
- POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token (anonymous
Orion token)
- GET /bmx-icons/* serving embedded static/media assets (media.bose.io)
- GET /ced/* serving embedded firmware index, release notes, and 10
app-help XMLs (downloads.bose.com)
Add media.bose.io and downloads.bose.com to DNS redirect lists (setup.go
both domain slices, dns.go shouldIntercept list, main.go getDomains
map). Document implemented endpoints in
tests/interactions_20260502_missing_external.md; mark rows 0246–0247 as
self/☑ in the interactions table.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace serial number-based directory structure with deviceId from device /info
- Extract migration logic to handle transition from old to new directory structure
- Fix directory resolution bug that prevented proper migration to deviceId-based paths
- Ensure all device data (Presets.xml, Sources.xml, Recents.xml) preserved during transition
- Add configurable migration with --migration-enabled and --migration-dry-run flags
- Update DeviceInfo.xml to reflect authoritative deviceId from device's /info endpoint
- Directory structure now: /devices/{deviceId}/ instead of /devices/{serialNumber}/
This aligns the directory structure with the device's self-declared identity
and ensures data consistency with the device's /info endpoint.
Enhances device identification by adding MAC address normalization and comprehensive documentation.
- Add `MAC-ADDRESS-MAPPING.md` guide explaining device identification and troubleshooting.
- Implement `normalizeMAC` in `DataStore` to handle various MAC formats (case-insensitive, with/without separators).
- Export `EnrichDeviceInfo` in UPnP discovery to allow better integration and testing.
- Update `TROUBLESHOOTING.md` with a new section on device identification issues.
- Add comprehensive integration and diagnostic tests for MAC mapping, case sensitivity, and UPnP discovery.
- Update documentation structure (`README.md`, `SUMMARY.md`) to include the new mapping guide.
Reduces `pkg/discovery` test suite runtime by ~75% (from ~17s to ~4s) by eliminating unnecessary network timeouts and reducing wait intervals.
- Refactor `discovery.Service` to use an injectable `http.Client`, allowing UPnP enrichment tests to use `httptest.Server` instead of waiting for 5s network timeouts.
- Make `DNSDiscovery` forward timeout configurable and reduce it from 2s to 100ms in unit tests.
- Decrease discovery and context timeouts in mDNS and Unified discovery tests to the minimum required for stable verification (typically 100-200ms).
Fixes an issue where device data (e.g., Presets.xml) could not be located when accessed via MAC address because the internal directory structure is organized by serial number.
- Add a `macToSerial` mapping in `DataStore` to bridge MAC addresses from API requests to internal serial-numbered directories.
- Implement automatic mapping population during `DataStore` initialization by scanning `DeviceInfo.xml` files.
- Update `AccountDeviceDir` to transparently resolve MAC addresses to serial numbers for file path construction.
- Enhance UPnP discovery to capture the MAC address (as `serialNumber` in the device description) for better device identification.
- Include automated tests for MAC-to-serial resolution and UPnP enrichment.
Major improvements to device discovery system:
🔧 **SSDP Discovery Fixed**:
- Fixed networking issue where SSDP used connected UDP socket instead of UDP listener
- SSDP now properly receives unicast responses from multicast requests
- UPnP discovery now works reliably and finds all MediaRenderer devices
✨ **Enhanced DiscoveredDevice Model**:
- Added consistent URL fields (APIBaseURL, InfoURL) for all discovery methods
- Added protocol-specific fields (UPnPLocation, UPnPUSN, MDNSHostname, etc.)
- Added DiscoveryMethod tracking to show how devices were found
- Added device merging support for same device found via multiple protocols
🚀 **Unified Discovery Improvements**:
- Fixed device merging logic to properly combine protocol-specific data
- Discovery methods now correctly show combinations like 'Configuration+SSDP/UPnP+mDNS/Bonjour'
- Removed duplicate configuration device loading in individual services
- All three discovery methods (SSDP, mDNS, Configuration) work together seamlessly
🛠 **Updated Tools & Examples**:
- Updated soundtouch-cli to display new consistent field structure
- Enhanced all example programs with better device information display
- Added new unified discovery example demonstrating all three methods
- Fixed context timeout issues in example programs
📋 **Comprehensive Testing**:
- All tests updated and passing
- Real-world validation with actual Bose SoundTouch devices
- Confirmed discovery methods properly merge device data
Every discovered device now has consistent http://host:port/info URLs regardless
of discovery method, while preserving valuable protocol-specific metadata.
- 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.
- Change '// Output:' to '// Example output:' in all examples
- Examples will still appear in pkg.go.dev documentation
- Prevents examples from running as tests and trying to connect to real devices
- Examples are for documentation purposes, not runtime testing
- Fix GetInfo() to GetDeviceInfo() in client examples
- Update discovery examples to use proper constructor patterns
- Fix Volume.Muted to Volume.MuteEnabled field reference
- Correct DiscoveredDevice field names (remove non-existent MACAddress)
- Fix ZoneMember to use IP field instead of IPAddress
- Update Presets examples to use Preset slice and proper methods
- Replace non-existent SubscribeToEvents with NewWebSocketClient pattern
- Fix Capabilities to use Capability field instead of Sources
- Remove duplicate example function names
- Ensure all examples compile and use correct API surface
- Add root package documentation with quick start guide and feature overview
- Enhance client package with detailed usage examples and API coverage
- Add comprehensive discovery package documentation with protocol explanations
- Create models package documentation explaining all data structures
- Add extensive example functions for all major use cases:
* Basic device control and playback
* Volume, bass, and balance management
* Source selection and preset handling
* Multiroom zone management
* Real-time WebSocket event monitoring
* Device discovery with UPnP and mDNS
* Error handling and context cancellation
- Include code examples for pkg.go.dev's example rendering
- Document API endpoints, data structures, and best practices
- Add hardware compatibility and implementation notes
- Reference original API documentation source from Bose Corporation
- Link to official Bose SoundTouch End-of-Life page
- Clarify this is an independent implementation
- Add disclaimer about non-affiliation with Bose Corporation
- Provide both online and local documentation references
- Fix range copy issues in cmd_network.go (use indexing instead of copying 168-byte structs)
- Rename DiscoveryService to Service to avoid package name stuttering
- Update all references to use new Service constructor names
- Apply automatic whitespace fixes using golangci-lint --fix
- Reduce linting issues from 32 to 7 (only cyclomatic complexity remains)
Remaining issues are architectural complexity violations that require manual refactoring.
- Fixed nil pointer dereference warnings by using t.Fatal instead of t.Error
- In unified_test.go: Changed service nil check to use t.Fatal
- In clockdisplay_test.go: Changed request nil check to use t.Fatal
Using t.Fatal ensures test execution stops if pointer is nil,
eliminating possibility of subsequent nil pointer dereference.
Progress: Eliminated all 5 staticcheck issues
Total issues: 21 → 16 (24% improvement)
Remaining:
- gocyclo: 14 (complexity - requires refactoring)
- revive: 1 (DiscoveryService naming)
- unparam: 1 (client.post result parameter)
- 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.
Features Added:
• mDNS/Bonjour discovery using hashicorp/mdns library
• Unified discovery service combining UPnP + mDNS + configuration
• Parallel discovery execution for optimal performance
• Comprehensive logging for both UPnP and mDNS discovery
• Network diagnostic tools for troubleshooting
New Discovery Methods:
• Configuration-based (fastest, most reliable)
• UPnP/SSDP discovery (widely supported, enhanced logging)
• mDNS/Bonjour discovery (Apple ecosystem friendly)
New Programs & Tools:
• cmd/example-mdns - Standalone mDNS discovery testing
• cmd/example-upnp - Isolated UPnP/SSDP discovery testing
• cmd/mdns-scanner - Network diagnostic tool for mDNS services
Enhanced Build System:
• make dev-mdns / dev-mdns-verbose (mDNS testing)
• make dev-upnp / dev-upnp-verbose (UPnP testing)
• make dev-scan-all (scan all network services)
• make dev-scan-soundtouch (scan for SoundTouch services)
Documentation:
• docs/DISCOVERY.md - Comprehensive discovery guide
• Updated README.md with new features and commands
• Full API documentation and troubleshooting guide
Technical Improvements:
• Detailed request/response logging for UPnP M-SEARCH
• Step-by-step mDNS service discovery tracking
• IP address resolution with IPv4/IPv6 handling
• Service name parsing and device info extraction
• Robust error handling and network diagnostics
Backward Compatibility:
• No breaking changes to existing APIs
• All existing tests pass
• CLI interface unchanged but enhanced
• Legacy UPnP-only service still available
- Implement HTTP client with XML support for SoundTouch Web API
- Add UPnP device discovery with SSDP protocol
- Create type-safe Go models for API responses
- Build CLI tool with device discovery and info commands
- Add comprehensive configuration management via .env and env vars
- Include extensive documentation (API endpoints, patterns, development guide)
- Translate all German documentation to English
- Set up modern Go project structure with testing framework
- Add Makefile for cross-platform builds and development workflow
Features:
✅ Device discovery (UPnP + manual configuration)
✅ Device information retrieval
✅ XML request/response handling
✅ CLI interface with flexible device targeting
✅ Cross-platform compatibility
✅ Comprehensive test coverage with mock data
✅ Production-ready configuration management