From c1d03dbf7c24960faea87151c4af0a925039f5d4 Mon Sep 17 00:00:00 2001 From: Stephan van Rooij <1292510+svrooij@users.noreply.github.com> Date: Thu, 7 Dec 2023 22:15:37 +0100 Subject: [PATCH] feat: Added Device Description parser --- src/Sonos.Base/Models/DeviceDescription.cs | 1361 ++++++++++++++++++++ src/Sonos.Base/SonosDevice.cs | 53 + src/Sonos.Cli/Commands/InfoCommand.cs | 5 +- 3 files changed, 1418 insertions(+), 1 deletion(-) create mode 100644 src/Sonos.Base/Models/DeviceDescription.cs diff --git a/src/Sonos.Base/Models/DeviceDescription.cs b/src/Sonos.Base/Models/DeviceDescription.cs new file mode 100644 index 0000000..1440268 --- /dev/null +++ b/src/Sonos.Base/Models/DeviceDescription.cs @@ -0,0 +1,1361 @@ +namespace Sonos.Base.Models; + + +// NOTE: Generated code may require at least .NET Framework 4.5 or .NET Core/Standard 2.0. +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType("root", AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +[System.Xml.Serialization.XmlRoot("root", Namespace = "urn:schemas-upnp-org:device-1-0", IsNullable = false)] +public partial class SonosDeviceDescription +{ + + private rootSpecVersion specVersionField; + + private rootDevice deviceField; + + /// + public rootSpecVersion specVersion + { + get + { + return this.specVersionField; + } + set + { + this.specVersionField = value; + } + } + + /// + public rootDevice device + { + get + { + return this.deviceField; + } + set + { + this.deviceField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootSpecVersion +{ + + private byte majorField; + + private byte minorField; + + /// + public byte major + { + get + { + return this.majorField; + } + set + { + this.majorField = value; + } + } + + /// + public byte minor + { + get + { + return this.minorField; + } + set + { + this.minorField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDevice +{ + + private string deviceTypeField; + + private string friendlyNameField; + + private string manufacturerField; + + private string manufacturerURLField; + + private string modelNumberField; + + private string modelDescriptionField; + + private string modelNameField; + + private string modelURLField; + + private string softwareVersionField; + + private byte swGenField; + + private string hardwareVersionField; + + private string serialNumField; + + private string mACAddressField; + + private string uDNField; + + private rootDeviceIconList iconListField; + + private string minCompatibleVersionField; + + private string legacyCompatibleVersionField; + + private string apiVersionField; + + private string minApiVersionField; + + private decimal displayVersionField; + + private object extraVersionField; + + private byte nsVersionField; + + private string roomNameField; + + private string displayNameField; + + private byte zoneTypeField; + + private string feature1Field; + + private string feature2Field; + + private string feature3Field; + + private string seriesidField; + + private byte variantField; + + private byte internalSpeakerSizeField; + + private ushort memoryField; + + private ushort flashField; + + private int ampOnTimeField; + + private int retailModeField; + + private ushort sSLPortField; + + private ushort securehhSSLPortField; + + private rootDeviceService[] serviceListField; + + private rootDeviceDevice[] deviceListField; + + /// + public string deviceType + { + get + { + return this.deviceTypeField; + } + set + { + this.deviceTypeField = value; + } + } + + /// + public string friendlyName + { + get + { + return this.friendlyNameField; + } + set + { + this.friendlyNameField = value; + } + } + + /// + public string manufacturer + { + get + { + return this.manufacturerField; + } + set + { + this.manufacturerField = value; + } + } + + /// + public string manufacturerURL + { + get + { + return this.manufacturerURLField; + } + set + { + this.manufacturerURLField = value; + } + } + + /// + public string modelNumber + { + get + { + return this.modelNumberField; + } + set + { + this.modelNumberField = value; + } + } + + /// + public string modelDescription + { + get + { + return this.modelDescriptionField; + } + set + { + this.modelDescriptionField = value; + } + } + + /// + public string modelName + { + get + { + return this.modelNameField; + } + set + { + this.modelNameField = value; + } + } + + /// + public string modelURL + { + get + { + return this.modelURLField; + } + set + { + this.modelURLField = value; + } + } + + /// + public string softwareVersion + { + get + { + return this.softwareVersionField; + } + set + { + this.softwareVersionField = value; + } + } + + /// + public byte swGen + { + get + { + return this.swGenField; + } + set + { + this.swGenField = value; + } + } + + /// + public string hardwareVersion + { + get + { + return this.hardwareVersionField; + } + set + { + this.hardwareVersionField = value; + } + } + + /// + public string serialNum + { + get + { + return this.serialNumField; + } + set + { + this.serialNumField = value; + } + } + + /// + public string MACAddress + { + get + { + return this.mACAddressField; + } + set + { + this.mACAddressField = value; + } + } + + /// + public string UDN + { + get + { + return this.uDNField; + } + set + { + this.uDNField = value; + } + } + + /// + public rootDeviceIconList iconList + { + get + { + return this.iconListField; + } + set + { + this.iconListField = value; + } + } + + /// + public string minCompatibleVersion + { + get + { + return this.minCompatibleVersionField; + } + set + { + this.minCompatibleVersionField = value; + } + } + + /// + public string legacyCompatibleVersion + { + get + { + return this.legacyCompatibleVersionField; + } + set + { + this.legacyCompatibleVersionField = value; + } + } + + /// + public string apiVersion + { + get + { + return this.apiVersionField; + } + set + { + this.apiVersionField = value; + } + } + + /// + public string minApiVersion + { + get + { + return this.minApiVersionField; + } + set + { + this.minApiVersionField = value; + } + } + + /// + public decimal displayVersion + { + get + { + return this.displayVersionField; + } + set + { + this.displayVersionField = value; + } + } + + /// + public object extraVersion + { + get + { + return this.extraVersionField; + } + set + { + this.extraVersionField = value; + } + } + + /// + public byte nsVersion + { + get + { + return this.nsVersionField; + } + set + { + this.nsVersionField = value; + } + } + + /// + public string roomName + { + get + { + return this.roomNameField; + } + set + { + this.roomNameField = value; + } + } + + /// + public string displayName + { + get + { + return this.displayNameField; + } + set + { + this.displayNameField = value; + } + } + + /// + public byte zoneType + { + get + { + return this.zoneTypeField; + } + set + { + this.zoneTypeField = value; + } + } + + /// + public string feature1 + { + get + { + return this.feature1Field; + } + set + { + this.feature1Field = value; + } + } + + /// + public string feature2 + { + get + { + return this.feature2Field; + } + set + { + this.feature2Field = value; + } + } + + /// + public string feature3 + { + get + { + return this.feature3Field; + } + set + { + this.feature3Field = value; + } + } + + /// + public string seriesid + { + get + { + return this.seriesidField; + } + set + { + this.seriesidField = value; + } + } + + /// + public byte variant + { + get + { + return this.variantField; + } + set + { + this.variantField = value; + } + } + + /// + public byte internalSpeakerSize + { + get + { + return this.internalSpeakerSizeField; + } + set + { + this.internalSpeakerSizeField = value; + } + } + + /// + public ushort memory + { + get + { + return this.memoryField; + } + set + { + this.memoryField = value; + } + } + + /// + public ushort flash + { + get + { + return this.flashField; + } + set + { + this.flashField = value; + } + } + + /// + public int ampOnTime + { + get + { + return this.ampOnTimeField; + } + set + { + this.ampOnTimeField = value; + } + } + + /// + public int retailMode + { + get + { + return this.retailModeField; + } + set + { + this.retailModeField = value; + } + } + + /// + public ushort SSLPort + { + get + { + return this.sSLPortField; + } + set + { + this.sSLPortField = value; + } + } + + /// + public ushort securehhSSLPort + { + get + { + return this.securehhSSLPortField; + } + set + { + this.securehhSSLPortField = value; + } + } + + /// + [System.Xml.Serialization.XmlArrayItem("service", IsNullable = false)] + public rootDeviceService[] serviceList + { + get + { + return this.serviceListField; + } + set + { + this.serviceListField = value; + } + } + + /// + [System.Xml.Serialization.XmlArrayItem("device", IsNullable = false)] + public rootDeviceDevice[] deviceList + { + get + { + return this.deviceListField; + } + set + { + this.deviceListField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceIconList +{ + + private rootDeviceIconListIcon iconField; + + /// + public rootDeviceIconListIcon icon + { + get + { + return this.iconField; + } + set + { + this.iconField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceIconListIcon +{ + + private byte idField; + + private string mimetypeField; + + private byte widthField; + + private byte heightField; + + private byte depthField; + + private string urlField; + + /// + public byte id + { + get + { + return this.idField; + } + set + { + this.idField = value; + } + } + + /// + public string mimetype + { + get + { + return this.mimetypeField; + } + set + { + this.mimetypeField = value; + } + } + + /// + public byte width + { + get + { + return this.widthField; + } + set + { + this.widthField = value; + } + } + + /// + public byte height + { + get + { + return this.heightField; + } + set + { + this.heightField = value; + } + } + + /// + public byte depth + { + get + { + return this.depthField; + } + set + { + this.depthField = value; + } + } + + /// + public string url + { + get + { + return this.urlField; + } + set + { + this.urlField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceService +{ + + private string serviceTypeField; + + private string serviceIdField; + + private string controlURLField; + + private string eventSubURLField; + + private string sCPDURLField; + + /// + public string serviceType + { + get + { + return this.serviceTypeField; + } + set + { + this.serviceTypeField = value; + } + } + + /// + public string serviceId + { + get + { + return this.serviceIdField; + } + set + { + this.serviceIdField = value; + } + } + + /// + public string controlURL + { + get + { + return this.controlURLField; + } + set + { + this.controlURLField = value; + } + } + + /// + public string eventSubURL + { + get + { + return this.eventSubURLField; + } + set + { + this.eventSubURLField = value; + } + } + + /// + public string SCPDURL + { + get + { + return this.sCPDURLField; + } + set + { + this.sCPDURLField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceDevice +{ + + private string deviceTypeField; + + private string friendlyNameField; + + private string manufacturerField; + + private string manufacturerURLField; + + private string modelNumberField; + + private string modelDescriptionField; + + private string modelNameField; + + private string modelURLField; + + private string uDNField; + + private rootDeviceDeviceService[] serviceListField; + + private X_RhapsodyExtension x_RhapsodyExtensionField; + + private string x_QPlay_SoftwareCapabilityField; + + private rootDeviceDeviceIconList iconListField; + + /// + public string deviceType + { + get + { + return this.deviceTypeField; + } + set + { + this.deviceTypeField = value; + } + } + + /// + public string friendlyName + { + get + { + return this.friendlyNameField; + } + set + { + this.friendlyNameField = value; + } + } + + /// + public string manufacturer + { + get + { + return this.manufacturerField; + } + set + { + this.manufacturerField = value; + } + } + + /// + public string manufacturerURL + { + get + { + return this.manufacturerURLField; + } + set + { + this.manufacturerURLField = value; + } + } + + /// + public string modelNumber + { + get + { + return this.modelNumberField; + } + set + { + this.modelNumberField = value; + } + } + + /// + public string modelDescription + { + get + { + return this.modelDescriptionField; + } + set + { + this.modelDescriptionField = value; + } + } + + /// + public string modelName + { + get + { + return this.modelNameField; + } + set + { + this.modelNameField = value; + } + } + + /// + public string modelURL + { + get + { + return this.modelURLField; + } + set + { + this.modelURLField = value; + } + } + + /// + public string UDN + { + get + { + return this.uDNField; + } + set + { + this.uDNField = value; + } + } + + /// + [System.Xml.Serialization.XmlArrayItem("service", IsNullable = false)] + public rootDeviceDeviceService[] serviceList + { + get + { + return this.serviceListField; + } + set + { + this.serviceListField = value; + } + } + + /// + [System.Xml.Serialization.XmlElement("X_Rhapsody-Extension", Namespace = "http://www.real.com/rhapsody/xmlns/upnp-1-0")] + public X_RhapsodyExtension X_RhapsodyExtension + { + get + { + return this.x_RhapsodyExtensionField; + } + set + { + this.x_RhapsodyExtensionField = value; + } + } + + /// + [System.Xml.Serialization.XmlElement(Namespace = "http://www.tencent.com")] + public string X_QPlay_SoftwareCapability + { + get + { + return this.x_QPlay_SoftwareCapabilityField; + } + set + { + this.x_QPlay_SoftwareCapabilityField = value; + } + } + + /// + public rootDeviceDeviceIconList iconList + { + get + { + return this.iconListField; + } + set + { + this.iconListField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceDeviceService +{ + + private string serviceTypeField; + + private string serviceIdField; + + private string controlURLField; + + private string eventSubURLField; + + private string sCPDURLField; + + /// + public string serviceType + { + get + { + return this.serviceTypeField; + } + set + { + this.serviceTypeField = value; + } + } + + /// + public string serviceId + { + get + { + return this.serviceIdField; + } + set + { + this.serviceIdField = value; + } + } + + /// + public string controlURL + { + get + { + return this.controlURLField; + } + set + { + this.controlURLField = value; + } + } + + /// + public string eventSubURL + { + get + { + return this.eventSubURLField; + } + set + { + this.eventSubURLField = value; + } + } + + /// + public string SCPDURL + { + get + { + return this.sCPDURLField; + } + set + { + this.sCPDURLField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://www.real.com/rhapsody/xmlns/upnp-1-0")] +[System.Xml.Serialization.XmlRoot("X_Rhapsody-Extension", Namespace = "http://www.real.com/rhapsody/xmlns/upnp-1-0", IsNullable = false)] +public partial class X_RhapsodyExtension +{ + + private string deviceIDField; + + private X_RhapsodyExtensionDeviceCapabilities deviceCapabilitiesField; + + /// + public string deviceID + { + get + { + return this.deviceIDField; + } + set + { + this.deviceIDField = value; + } + } + + /// + public X_RhapsodyExtensionDeviceCapabilities deviceCapabilities + { + get + { + return this.deviceCapabilitiesField; + } + set + { + this.deviceCapabilitiesField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://www.real.com/rhapsody/xmlns/upnp-1-0")] +public partial class X_RhapsodyExtensionDeviceCapabilities +{ + + private X_RhapsodyExtensionDeviceCapabilitiesInteractionPattern interactionPatternField; + + /// + public X_RhapsodyExtensionDeviceCapabilitiesInteractionPattern interactionPattern + { + get + { + return this.interactionPatternField; + } + set + { + this.interactionPatternField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://www.real.com/rhapsody/xmlns/upnp-1-0")] +public partial class X_RhapsodyExtensionDeviceCapabilitiesInteractionPattern +{ + + private string typeField; + + /// + [System.Xml.Serialization.XmlAttribute()] + public string type + { + get + { + return this.typeField; + } + set + { + this.typeField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceDeviceIconList +{ + + private rootDeviceDeviceIconListIcon iconField; + + /// + public rootDeviceDeviceIconListIcon icon + { + get + { + return this.iconField; + } + set + { + this.iconField = value; + } + } +} + +/// +[System.Serializable()] +[System.ComponentModel.DesignerCategory("code")] +[System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "urn:schemas-upnp-org:device-1-0")] +public partial class rootDeviceDeviceIconListIcon +{ + + private string mimetypeField; + + private byte widthField; + + private byte heightField; + + private byte depthField; + + private string urlField; + + /// + public string mimetype + { + get + { + return this.mimetypeField; + } + set + { + this.mimetypeField = value; + } + } + + /// + public byte width + { + get + { + return this.widthField; + } + set + { + this.widthField = value; + } + } + + /// + public byte height + { + get + { + return this.heightField; + } + set + { + this.heightField = value; + } + } + + /// + public byte depth + { + get + { + return this.depthField; + } + set + { + this.depthField = value; + } + } + + /// + public string url + { + get + { + return this.urlField; + } + set + { + this.urlField = value; + } + } +} + diff --git a/src/Sonos.Base/SonosDevice.cs b/src/Sonos.Base/SonosDevice.cs index a42a9cb..6a2513f 100644 --- a/src/Sonos.Base/SonosDevice.cs +++ b/src/Sonos.Base/SonosDevice.cs @@ -22,6 +22,7 @@ namespace Sonos.Base; using Sonos.Base.Services; using Sonos.Base.Internal; using System.Threading.Tasks; +using System.Xml.Serialization; public partial class SonosDevice : IDisposable, IAsyncDisposable { @@ -55,7 +56,31 @@ public SonosDevice(SonosDeviceOptions options) } internal SonosServiceOptions ServiceOptions { get; private set; } + + /// + /// Gets the device properties service. + /// + /// + /// + /// SonosDeviceDescription is generated with Paste XML as Classes + public async Task GetDeviceDescriptionAsync(CancellationToken cancellationToken = default) + { + var uri = new Uri(ServiceOptions.DeviceUri, "/xml/device_description.xml"); + var response = await ServiceOptions.ServiceProvider.GetHttpClient().GetAsync(uri, cancellationToken); + response.EnsureSuccessStatusCode(); + + var serializer = new XmlSerializer(typeof(Models.SonosDeviceDescription)); + using (var reader = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken))) + { + var deviceDescription = (Models.SonosDeviceDescription)serializer.Deserialize(reader)!; + return deviceDescription; + } + } + /// + /// Loads the uuid from the device if it is not set. + /// + /// public async Task LoadUuidAsync(CancellationToken cancellationToken = default) { if (!Uuid.StartsWith("RINCON")) @@ -65,6 +90,14 @@ public async Task LoadUuidAsync(CancellationToken cancellationToken = default) } } + /// + /// Send a notification to the speaker. + /// + /// + /// + /// + /// Throws when volume is not between 1 and 100 + /// This method is using the native Sonos notification system, which is only available on S2 devices. public async Task QueueNotificationAsync(NotificationOptions notificationOptions, CancellationToken cancellationToken = default) { //TODO Check if speaker is playing else skip @@ -83,14 +116,34 @@ public async Task QueueNotificationAsync(NotificationOptions notificationO #region Shortcuts + /// + /// Shortcut to , on the coordinator. + /// + /// public Task NextAsync(CancellationToken cancellationToken = default) => Coordinator.AVTransportService.NextAsync(cancellationToken); + /// + /// Shortcut to , on the coordinator. + /// + /// public Task PauseAsync(CancellationToken cancellationToken = default) => Coordinator.AVTransportService.PauseAsync(cancellationToken); + /// + /// Shortcut to , on the coordinator. + /// + /// public Task PlayAsync(CancellationToken cancellationToken = default) => Coordinator.AVTransportService.PlayAsync(cancellationToken); + /// + /// Shortcut to , on the coordinator. + /// + /// public Task PreviousAsync(CancellationToken cancellationToken = default) => Coordinator.AVTransportService.PreviousAsync(cancellationToken); + /// + /// Shortcut to , on the coordinator. + /// + /// public Task StopAsync(CancellationToken cancellationToken = default) => Coordinator.AVTransportService.StopAsync(cancellationToken); #endregion Shortcuts diff --git a/src/Sonos.Cli/Commands/InfoCommand.cs b/src/Sonos.Cli/Commands/InfoCommand.cs index 8faad3e..e9c1d4e 100644 --- a/src/Sonos.Cli/Commands/InfoCommand.cs +++ b/src/Sonos.Cli/Commands/InfoCommand.cs @@ -12,9 +12,9 @@ public enum SonosInfo { Position = 1, Transport = 2, - //Volume = 3, Media = 4, + DeviceDescription = 5, } public static Command GetCommand() @@ -47,6 +47,9 @@ private static async Task Run(InfoCommandOptions options, IHost host) case SonosInfo.Media: CommandHelpers.WriteJson(await sonos.AVTransportService.GetMediaInfoAsync()); break; + case SonosInfo.DeviceDescription: + CommandHelpers.WriteJson(await sonos.GetDeviceDescriptionAsync()); + break; } }