Skip to main content

sonos_api/
service.rs

1/// Represents the different UPnP services exposed by Sonos devices
2///
3/// Each service provides a specific set of operations for controlling different
4/// aspects of the Sonos device functionality.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum Service {
7    /// AVTransport service - Controls playback (play, pause, stop, seek, etc.)
8    AVTransport,
9
10    /// RenderingControl service - Controls audio rendering (volume, mute, etc.)
11    RenderingControl,
12
13    /// GroupRenderingControl service - Controls group-wide audio settings
14    GroupRenderingControl,
15
16    /// ZoneGroupTopology service - Manages speaker grouping and topology
17    ZoneGroupTopology,
18
19    /// GroupManagement service - Manages speaker group membership operations
20    GroupManagement,
21}
22
23/// Contains the endpoint and service URI information for a UPnP service
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct ServiceInfo {
26    /// The HTTP endpoint path for this service (relative to device base URL)
27    pub endpoint: &'static str,
28
29    /// The UPnP service URI used in SOAP requests
30    pub service_uri: &'static str,
31
32    /// The HTTP event endpoint path for UPnP event subscriptions
33    pub event_endpoint: &'static str,
34}
35
36/// Defines the subscription scope for UPnP services
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
38pub enum ServiceScope {
39    /// Per-speaker service - allows independent subscriptions on each speaker
40    PerSpeaker,
41    /// Per-network service - only one subscription should exist across entire network
42    PerNetwork,
43    /// Per-coordinator service - only run on speakers that are the coordinator for a group
44    PerCoordinator,
45}
46
47impl Service {
48    /// Get the name of this service as a string
49    ///
50    /// # Returns
51    /// The service name as a string slice
52    pub fn name(&self) -> &'static str {
53        match self {
54            Service::AVTransport => "AVTransport",
55            Service::RenderingControl => "RenderingControl",
56            Service::GroupRenderingControl => "GroupRenderingControl",
57            Service::ZoneGroupTopology => "ZoneGroupTopology",
58            Service::GroupManagement => "GroupManagement",
59        }
60    }
61
62    /// Get the service information (endpoint and URI) for this service
63    ///
64    /// # Returns
65    /// A `ServiceInfo` struct containing the endpoint path, service URI, and event endpoint
66    pub fn info(&self) -> ServiceInfo {
67        match self {
68            Service::AVTransport => ServiceInfo {
69                endpoint: "MediaRenderer/AVTransport/Control",
70                service_uri: "urn:schemas-upnp-org:service:AVTransport:1",
71                event_endpoint: "MediaRenderer/AVTransport/Event",
72            },
73            Service::RenderingControl => ServiceInfo {
74                endpoint: "MediaRenderer/RenderingControl/Control",
75                service_uri: "urn:schemas-upnp-org:service:RenderingControl:1",
76                event_endpoint: "MediaRenderer/RenderingControl/Event",
77            },
78            Service::GroupRenderingControl => ServiceInfo {
79                endpoint: "MediaRenderer/GroupRenderingControl/Control",
80                service_uri: "urn:schemas-upnp-org:service:GroupRenderingControl:1",
81                event_endpoint: "MediaRenderer/GroupRenderingControl/Event",
82            },
83            Service::ZoneGroupTopology => ServiceInfo {
84                endpoint: "ZoneGroupTopology/Control",
85                service_uri: "urn:schemas-upnp-org:service:ZoneGroupTopology:1",
86                event_endpoint: "ZoneGroupTopology/Event",
87            },
88            Service::GroupManagement => ServiceInfo {
89                endpoint: "GroupManagement/Control",
90                service_uri: "urn:schemas-upnp-org:service:GroupManagement:1",
91                event_endpoint: "GroupManagement/Event",
92            },
93        }
94    }
95
96    /// Get the subscription scope for this service
97    ///
98    /// # Returns
99    /// A `ServiceScope` indicating whether this service should have per-speaker,
100    /// per-network, or per-coordinator subscriptions
101    pub fn scope(&self) -> ServiceScope {
102        match self {
103            Service::AVTransport => ServiceScope::PerSpeaker,
104            Service::RenderingControl => ServiceScope::PerSpeaker,
105            Service::GroupRenderingControl => ServiceScope::PerCoordinator,
106            Service::ZoneGroupTopology => ServiceScope::PerNetwork,
107            Service::GroupManagement => ServiceScope::PerCoordinator,
108        }
109    }
110}
111
112#[cfg(test)]
113mod scope_tests {
114    use super::*;
115
116    #[test]
117    fn test_service_scopes() {
118        assert_eq!(Service::AVTransport.scope(), ServiceScope::PerSpeaker);
119        assert_eq!(Service::RenderingControl.scope(), ServiceScope::PerSpeaker);
120        assert_eq!(
121            Service::GroupRenderingControl.scope(),
122            ServiceScope::PerCoordinator
123        );
124        assert_eq!(Service::ZoneGroupTopology.scope(), ServiceScope::PerNetwork);
125        assert_eq!(
126            Service::GroupManagement.scope(),
127            ServiceScope::PerCoordinator
128        );
129    }
130
131    #[test]
132    fn test_all_services_have_scope() {
133        // Ensure new services added to enum get scope assignments
134        let services = [
135            Service::AVTransport,
136            Service::RenderingControl,
137            Service::GroupRenderingControl,
138            Service::ZoneGroupTopology,
139            Service::GroupManagement,
140        ];
141
142        for service in services {
143            let _scope = service.scope(); // Should not panic
144        }
145    }
146}