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