feat: implement comprehensive /supportedURLs endpoint with feature mapping system

 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.
This commit is contained in:
Tobias Gesellchen
2026-01-31 20:23:30 +01:00
parent 4ebc42f5d5
commit 83e289ab38
24 changed files with 4936 additions and 50 deletions
+20
View File
@@ -0,0 +1,20 @@
# Compiled binaries
service-availability
service-availability.exe
# Build artifacts
*.o
*.a
*.so
# Temporary files
*.tmp
*.temp
# IDE files
.vscode/
.idea/
# OS specific
.DS_Store
Thumbs.db
+153
View File
@@ -0,0 +1,153 @@
# Service Availability Example
This example demonstrates how to use the `GetServiceAvailability()` method to retrieve and analyze service availability from a Bose SoundTouch device. This information can be used to provide better user feedback about supported stations and sources.
## What is Service Availability?
The `/serviceAvailability` endpoint provides information about which music services and input sources are theoretically available on the device, along with reasons why certain services might be unavailable.
This is different from the `/sources` endpoint, which shows currently configured and ready sources. Service availability shows what's possible, while sources show what's currently set up.
## Running the Example
### Method 1: Command Line Argument
```bash
go run main.go 192.168.1.100
```
### Method 2: Environment Variable
```bash
SOUNDTOUCH_HOST=192.168.1.100 go run main.go
```
Replace `192.168.1.100` with your SoundTouch device's IP address.
## Example Output
```
============================================================
SOUNDTOUCH SERVICE AVAILABILITY REPORT
============================================================
Total Services: 13
Available Services: 9
Unavailable Services: 4
📱 AVAILABLE SERVICES:
✅ AirPlay
✅ Amazon Music
✅ Deezer
✅ iHeartRadio
✅ Internet Radio
✅ Local Music Library
✅ Pandora
✅ Spotify
✅ TuneIn Radio
❌ UNAVAILABLE SERVICES:
❌ Amazon Alexa
❌ Bluetooth (INVALID_SOURCE_TYPE)
❌ BMX
❌ Notifications
🎵 STREAMING SERVICES:
✅ Spotify
✅ Pandora
✅ TuneIn Radio
✅ Amazon Music
✅ Deezer
✅ iHeartRadio
✅ Internet Radio
Summary: 7/7 streaming services available
🔗 LOCAL INPUT SERVICES:
❌ Bluetooth
✅ AirPlay
✅ Local Music Library
Summary: 2/3 local services available
```
## Key Features Demonstrated
### 1. Service Availability Analysis
- Total service count and availability breakdown
- Categorization into streaming vs. local services
- Detailed status for each service type
### 2. User-Friendly Recommendations
- Smart suggestions based on available services
- Alternative recommendations when preferred services are unavailable
- Clear status indicators for popular services
### 3. Troubleshooting Information
- Specific reasons why services are unavailable
- Helpful tips for resolving common issues
- Service-specific guidance
### 4. Comparison with Configured Sources
- Side-by-side comparison with the `/sources` endpoint
- Identification of available but unconfigured services
- Guidance on setting up available services
## Use Cases
### Application Development
Use this information to:
- Show users which music services they can potentially use
- Provide helpful setup guidance for available but unconfigured services
- Display appropriate UI elements based on device capabilities
- Offer fallback options when preferred services are unavailable
### User Support
- Diagnose why certain services aren't working
- Provide specific troubleshooting steps
- Help users understand their device's capabilities
- Guide users through service setup
### Device Management
- Audit service capabilities across multiple devices
- Plan music service deployments
- Understand device limitations
## API Methods Used
This example demonstrates several key methods from the ServiceAvailability API:
```go
// Get service availability
serviceAvailability, err := client.GetServiceAvailability()
// Check specific services
hasSpotify := serviceAvailability.HasSpotify()
hasBluetooth := serviceAvailability.HasBluetooth()
// Get service details
spotifyService := serviceAvailability.GetServiceByType(models.ServiceTypeSpotify)
if spotifyService != nil && !spotifyService.IsAvailable {
reason := spotifyService.GetReason()
}
// Get categorized services
streamingServices := serviceAvailability.GetStreamingServices()
localServices := serviceAvailability.GetLocalServices()
// Get availability counts
total := serviceAvailability.GetServiceCount()
available := serviceAvailability.GetAvailableServiceCount()
unavailable := serviceAvailability.GetUnavailableServiceCount()
```
## Integration Ideas
This functionality can be integrated into:
- Mobile apps to show service status
- Web dashboards for device management
- Setup wizards for new devices
- Troubleshooting tools
- Music service recommendation systems
## Notes
- Service availability may change based on device firmware, network connectivity, and account status
- Some services may show as available but require additional setup (like signing into streaming accounts)
- The `reason` field provides valuable context for why services are unavailable
- Always compare with the `/sources` endpoint for a complete picture of device capabilities
+295
View File
@@ -0,0 +1,295 @@
// Package main demonstrates service availability checking for SoundTouch devices
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/gesellix/bose-soundtouch/pkg/client"
"github.com/gesellix/bose-soundtouch/pkg/models"
)
func main() {
// Get SoundTouch device host from command line argument or environment variable
host := getSoundTouchHost()
if host == "" {
fmt.Println("Usage: go run main.go <soundtouch-host>")
fmt.Println(" or: SOUNDTOUCH_TEST_HOST=192.168.1.100 go run main.go")
os.Exit(1)
}
// Create client
soundtouchClient := client.NewClientFromHost(host)
// Get service availability
serviceAvailability, err := soundtouchClient.GetServiceAvailability()
if err != nil {
log.Fatalf("Failed to get service availability: %v", err)
}
// Display comprehensive service availability report
displayServiceReport(serviceAvailability)
// Show practical usage examples
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println("PRACTICAL USAGE EXAMPLES")
fmt.Println(strings.Repeat("=", 60))
demonstrateUserFeedback(serviceAvailability, soundtouchClient)
}
func getSoundTouchHost() string {
// Check command line arguments first
if len(os.Args) > 1 {
return os.Args[1]
}
// Fall back to environment variable
return os.Getenv("SOUNDTOUCH_TEST_HOST")
}
func displayServiceReport(sa *models.ServiceAvailability) {
fmt.Println(strings.Repeat("=", 60))
fmt.Println("SOUNDTOUCH SERVICE AVAILABILITY REPORT")
fmt.Println(strings.Repeat("=", 60))
if sa.Services == nil {
fmt.Println("No service information available")
return
}
// Summary statistics
fmt.Printf("Total Services: %d\n", sa.GetServiceCount())
fmt.Printf("Available Services: %d\n", sa.GetAvailableServiceCount())
fmt.Printf("Unavailable Services: %d\n", sa.GetUnavailableServiceCount())
// Available services
fmt.Println("\n📱 AVAILABLE SERVICES:")
availableServices := sa.GetAvailableServices()
if len(availableServices) == 0 {
fmt.Println(" None")
} else {
for _, service := range availableServices {
fmt.Printf(" ✅ %s\n", formatServiceName(service.Type))
}
}
// Unavailable services
fmt.Println("\n❌ UNAVAILABLE SERVICES:")
unavailableServices := sa.GetUnavailableServices()
if len(unavailableServices) == 0 {
fmt.Println(" None")
} else {
for _, service := range unavailableServices {
reason := ""
if service.Reason != "" {
reason = fmt.Sprintf(" (%s)", service.Reason)
}
fmt.Printf(" ❌ %s%s\n", formatServiceName(service.Type), reason)
}
}
// Category breakdowns
displayServiceCategories(sa)
// Quick status checks
displayQuickStatusChecks(sa)
}
func displayServiceCategories(sa *models.ServiceAvailability) {
fmt.Println("\n🎵 STREAMING SERVICES:")
streamingServices := sa.GetStreamingServices()
availableCount := 0
for _, service := range streamingServices {
status := "❌"
if service.IsAvailable {
status = "✅"
availableCount++
}
fmt.Printf(" %s %s\n", status, formatServiceName(service.Type))
}
fmt.Printf(" Summary: %d/%d streaming services available\n", availableCount, len(streamingServices))
fmt.Println("\n🔗 LOCAL INPUT SERVICES:")
localServices := sa.GetLocalServices()
localAvailableCount := 0
for _, service := range localServices {
status := "❌"
if service.IsAvailable {
status = "✅"
localAvailableCount++
}
fmt.Printf(" %s %s\n", status, formatServiceName(service.Type))
}
fmt.Printf(" Summary: %d/%d local services available\n", localAvailableCount, len(localServices))
}
func displayQuickStatusChecks(sa *models.ServiceAvailability) {
fmt.Println("\n⚡ QUICK STATUS CHECKS:")
checks := []struct {
name string
check func() bool
icon string
}{
{"Spotify Ready", sa.HasSpotify, "🎵"},
{"Bluetooth Ready", sa.HasBluetooth, "🔵"},
{"AirPlay Ready", sa.HasAirPlay, "📡"},
{"Alexa Ready", sa.HasAlexa, "🗣️"},
{"TuneIn Ready", sa.HasTuneIn, "📻"},
{"Pandora Ready", sa.HasPandora, "🎼"},
{"Local Music Ready", sa.HasLocalMusic, "💾"},
}
for _, check := range checks {
status := "❌ Not Available"
if check.check() {
status = "✅ Available"
}
fmt.Printf(" %s %s: %s\n", check.icon, check.name, status)
}
}
func demonstrateUserFeedback(sa *models.ServiceAvailability, soundtouchClient *client.Client) {
fmt.Println("\n1. SMART MUSIC SOURCE RECOMMENDATIONS:")
recommendMusicSources(sa)
fmt.Println("\n2. TROUBLESHOOTING UNAVAILABLE SERVICES:")
provideTroubleshootingInfo(sa)
fmt.Println("\n3. COMPARISON WITH CONFIGURED SOURCES:")
compareWithConfiguredSources(sa, soundtouchClient)
}
func recommendMusicSources(sa *models.ServiceAvailability) {
if sa.HasSpotify() {
fmt.Println(" 🎵 Spotify is available - you can stream from your Spotify account")
}
if sa.HasBluetooth() {
fmt.Println(" 🔵 Bluetooth is available - you can pair your phone or device")
} else {
fmt.Println(" 🔵 Bluetooth is not available - check if Bluetooth is enabled on your device")
}
if sa.HasAirPlay() {
fmt.Println(" 📡 AirPlay is available - you can stream from Apple devices")
}
if sa.HasTuneIn() {
fmt.Println(" 📻 TuneIn Radio is available - you can listen to internet radio stations")
}
if sa.HasLocalMusic() {
fmt.Println(" 💾 Local Music is available - you can access music from network storage")
}
// Suggest alternatives if main services are unavailable
if !sa.HasSpotify() && !sa.HasBluetooth() && sa.HasTuneIn() {
fmt.Println(" 💡 Consider using TuneIn Radio as an alternative music source")
}
}
func provideTroubleshootingInfo(sa *models.ServiceAvailability) {
unavailableServices := sa.GetUnavailableServices()
for _, service := range unavailableServices {
switch service.Type {
case "BLUETOOTH":
fmt.Printf(" 🔵 Bluetooth: %s\n", getTroubleshootingTip("BLUETOOTH", service.Reason))
case "SPOTIFY":
fmt.Printf(" 🎵 Spotify: %s\n", getTroubleshootingTip("SPOTIFY", service.Reason))
case "ALEXA":
fmt.Printf(" 🗣️ Alexa: %s\n", getTroubleshootingTip("ALEXA", service.Reason))
case "AIRPLAY":
fmt.Printf(" 📡 AirPlay: %s\n", getTroubleshootingTip("AIRPLAY", service.Reason))
}
}
}
func getTroubleshootingTip(serviceType, reason string) string {
switch serviceType {
case "BLUETOOTH":
if reason == "INVALID_SOURCE_TYPE" {
return "This device may not support Bluetooth audio input"
}
return "Check if Bluetooth is enabled and try restarting the device"
case "SPOTIFY":
return "Ensure you have a Spotify Premium account and are logged in"
case "ALEXA":
return "Check if Amazon Alexa is properly set up and connected"
case "AIRPLAY":
return "Ensure your Apple device and SoundTouch are on the same network"
default:
if reason != "" {
return fmt.Sprintf("Reason: %s", reason)
}
return "Service is currently unavailable"
}
}
func compareWithConfiguredSources(sa *models.ServiceAvailability, soundtouchClient *client.Client) {
sources, err := soundtouchClient.GetSources()
if err != nil {
fmt.Printf(" ❌ Could not retrieve configured sources: %v\n", err)
return
}
fmt.Println(" Comparing service availability with configured sources:")
// Check Spotify
spotifyAvailable := sa.HasSpotify()
spotifyConfigured := sources.HasSpotify()
fmt.Printf(" 🎵 Spotify - Available: %v, Configured: %v\n", spotifyAvailable, spotifyConfigured)
if spotifyAvailable && !spotifyConfigured {
fmt.Println(" 💡 Spotify is available but not configured - you may need to sign in")
}
// Check Bluetooth
bluetoothAvailable := sa.HasBluetooth()
bluetoothConfigured := sources.HasBluetooth()
fmt.Printf(" 🔵 Bluetooth - Available: %v, Configured: %v\n", bluetoothAvailable, bluetoothConfigured)
if bluetoothAvailable && !bluetoothConfigured {
fmt.Println(" 💡 Bluetooth is available but not configured - try pairing a device")
}
fmt.Printf("\n 📊 Total configured sources: %d\n", sources.GetSourceCount())
fmt.Printf(" 📊 Ready configured sources: %d\n", sources.GetReadySourceCount())
}
func formatServiceName(serviceType string) string {
switch serviceType {
case "SPOTIFY":
return "Spotify"
case "BLUETOOTH":
return "Bluetooth"
case "AIRPLAY":
return "AirPlay"
case "ALEXA":
return "Amazon Alexa"
case "AMAZON":
return "Amazon Music"
case "PANDORA":
return "Pandora"
case "TUNEIN":
return "TuneIn Radio"
case "DEEZER":
return "Deezer"
case "IHEART":
return "iHeartRadio"
case "LOCAL_INTERNET_RADIO":
return "Internet Radio"
case "LOCAL_MUSIC":
return "Local Music Library"
case "BMX":
return "BMX"
case "NOTIFICATION":
return "Notifications"
default:
return serviceType
}
}