pax_global_header00006660000000000000000000000064146554422020014516gustar00rootroot0000000000000052 comment=d96c7b5d6820cb4e7dfa7ba6e5136593a60c0c2c bazwilliams-openhomedevice-d96c7b5/000077500000000000000000000000001465544220200174515ustar00rootroot00000000000000bazwilliams-openhomedevice-d96c7b5/.gitignore000066400000000000000000000001151465544220200214360ustar00rootroot00000000000000env venv *.pyc .vscode dist MANIFEST *.egg-info .coverage lib bin pyvenv.cfg bazwilliams-openhomedevice-d96c7b5/LICENSE.txt000066400000000000000000000020641465544220200212760ustar00rootroot00000000000000Copyright 2017 Barry John Williams Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.bazwilliams-openhomedevice-d96c7b5/README.md000066400000000000000000000146761465544220200207460ustar00rootroot00000000000000# openhomedevice Library to provide an API to an existing openhome device. The device needs to have been discovered first by something like netdisco (https://github.com/home-assistant/netdisco). The underlying UPnP client library used is https://github.com/StevenLooman/async_upnp_client * Tested against [Linn Products Ltd](https://www.linn.co.uk/uk/) devices running Davaar 80 (thought expected to work on earlier variants) * Tested against [OpenHome Player](http://openhome.org/) devices ## Installation `pip install openhomedevice` ## API ### Constructor ```python device = Device(location) await device.init() ``` ### Methods #### Control ```python await set_standby(standbyRequested) #bool await play() #starts playback await play_media(track_details) #start playing `track_details` await stop() #stops playback await pause() #pauses playback await skip(offset) #positive or negative integer await set_volume(volume_level) #positive number await increase_volume() #increase volume by 1 await decrease_volume() #decrease volume by 1 await set_mute(muteRequested) #bool await set_source(index) #positive integer (use Sources() for indices) await invoke_pin(index) #positive integer (use Pins() for indices) ``` #### Firmware ```python await check_latest_firmware() #check for the latest firmware await update_firmware() #update the device firmware await software_status() #returns a dictionary with information about the current software ``` #### Informational ```python uuid() #Unique identifier manufacturer() #Manufacturer model_name() #Model Name friendly_name() #Friendly Name await name() #Name of device await room() #Name of room await is_in_standby() #returns true if in standby await transport_state() #returns one of Stopped, Playing, Paused or Buffering. volume_enabled #property true if the volume service is available await volume_level() #returns the volume setting or None if disabled await is_muted() #returns true if muted or None if disabled await source() #returns the currently connected source as a dictionary await sources() #returns an array of source dictionaries with indices await track_info() #returns a track dictionary await pins() #returns an array of pin dictionaries with indices pins_enabled #property true if the pins service is available ``` ##### Source Response ```python { 'type': 'Playlist', 'name': 'Playlist' } ``` ##### Sources Response ```python [ { 'index': 0, 'type': 'Playlist', 'name': 'Playlist' }, { 'index': 1, 'type': 'Radio', 'name': 'Radio' }, { 'index': 3, 'type': 'Receiver', 'name': 'Songcast' }, { 'index': 6, 'type': 'Analog', 'name': 'Front Aux' } ] ``` ##### Pins Response ```python [ {'index': 1, 'title': 'Playstation 4', 'artworkUri': 'external:///source?type=Hdmi&systemName=HDMI3'} {'index': 4, 'title': 'Classic FM', 'artworkUri': 'http://cdn-profiles.tunein.com/s8439/images/logoq.png?t=1'} {'index': 6, 'title': 'Chillout Playlist', 'artworkUri': 'http://media/artwork/chillout-playlist.png'} ] ``` ##### TrackInfo Response ```python { "mimeType": "http-get:*:audio/x-flac:DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000", "rating": None, "performer": [ "Fahmi Alqhai, Performer - Johann Sebastian Bach, Composer" ], "bitDepth": 16, "channels": 2, "disc": None, "composer": [], "year": 2017, "duration": 460, "author": [], "albumArtist": [], "type": "object.item.audioItem.musicTrack", "narrator": [], "description": None, "conductor": [], "albumArtwork": "http://static.qobuz.com/images/covers/58/20/8424562332058_600.jpg", "track": 2, "tracks": None, "artwork": None, "genre": [ "Klassiek" ], "publisher": "Glossa", "albumGenre": [ "Klassiek" ], "artist": [ "Fahmi Alqhai" ], "bitRate": None, "albumTitle": "The Bach Album", "uri": "http://192.168.0.110:58050/stream/audio/b362f0f7a1ff33b176bcf2adde75af96.flac", "discs": None, "published": None, "title": "Violin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba) : Violin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba): II. Fuga", "sampleRate": 44100 } ``` ##### SoftwareStatus response When an update is available: ```python { "status":"update_available", "current_software":{ "version":"4.99.491", "topic":"main", "channel":"release" }, "update_info":{ "legal":{ "licenseurl":"http://products.linn.co.uk/VersionInfo/licenseV2.txt", "privacyurl":"https://www.linn.co.uk/privacy", "privacyuri":"https://products.linn.co.uk/VersionInfo/PrivacyV1.json", "privacyversion":1 }, "releasenotesuri":"http://docs.linn.co.uk/wiki/index.php/ReleaseNotes", "updates":[ { "channel":"release", "date":"07 Jun 2023 12:29:48", "description":"Release build version 4.100.502 (07 Jun 2023 12:29:48)", "exaktlink":"3", "manifest":"https://cloud.linn.co.uk/update/components/836/4.100.502/manifest.json", "topic":"main", "variant":"836", "version":"4.100.502" } ], "exaktUpdates":[] } } ``` When the system is on the latest firmware: ```python { "status":"on_latest", "current_software":{ "version":"4.100.502", "topic":"main", "channel":"release" } } ``` ##### Upgrading Firmware Use this to check if an update is required and then instruct the device to apply it ```python await openhomeDevice.check_latest_firmware() await openhomeDevice.update_firmware() ``` ##### Playing A Track Use this to play a short audio track, a podcast Uri or radio station Uri. The audio will be played using the radio source of the device. The `trackDetails` object should be the same as the one described in the `TrackInfo` section above. ```python track_details = {} track_details["uri"] = "http://opml.radiotime.com/Tune.ashx?id=s122119" track_details["title"] = 'Linn Radio (Eclectic Music)' track_details["albumArtwork"] = 'http://cdn-radiotime-logos.tunein.com/s122119q.png' openhomeDevice.PlayMedia(track_details) ``` ## Example ```python python3 demo.py ``` ## Running Tests ```bash PYTHONPATH=. pytest ./tests/* ``` ## Uploading Package Following guide from https://packaging.python.org/tutorials/packaging-projects/ Update version in `setup.py` ```sh python3 setup.py sdist twine upload dist/* ``` bazwilliams-openhomedevice-d96c7b5/demo.py000066400000000000000000000052261465544220200207540ustar00rootroot00000000000000import asyncio from openhomedevice.device import Device import time async def main(): locations = [ #"http://192.168.1.32:53101/OsxPlayer-barrys-imac.local/Upnp/device.xml", "http://192.168.68.85:55178/4c494e4e-0026-0f21-bf92-01303737013f/Upnp/device.xml" # "http://192.168.4x.64:55178/4c494e4e-0026-0f22-2963-01387403013f/Upnp/device.xml" ] for location in locations: device = Device(location) await device.init() # await device.setup_subscriptions() print("----") print("NAME : %s" % await device.name()) print("ROOM : %s" % await device.room()) print("UUID : %s" % device.uuid()) print("SOURCE : %s" % await device.source()) print("STANDBY : %s" % await device.is_in_standby()) print("STATE : %s" % await device.transport_state()) print("TRACK : %s" % await device.track_info()) print("HAS VOL : %s" % device.volume_enabled) print("VOLUME : %s" % await device.volume()) print("MUTED : %s" % await device.is_muted()) print("SOURCES : %s" % await device.sources()) print("HAS PINS : %s" % device.pins_enabled) print("PINS : %s" % await device.pins()) print("SOFTWARE : %s" % await device.software_status()) #await device.check_latest_firmware() #await device.update_firmware() await device.set_standby(False) await device.set_volume(30) await device.increase_volume() await device.decrease_volume() await device.set_mute(True) await device.set_mute(False) await device.play_media({ "uri": "http://opml.radiotime.com/Tune.ashx?id=s50646&formats=mp3,aac,ogg,hls&partnerId=ah2rjr68&username=linnproducts&c=ebrowse"}) time.sleep(4) print("TRACK : %s" % await device.track_info()) print("STATE : %s" % await device.transport_state()) await device.pause() await device.play() await device.invoke_pin(1) time.sleep(4) print("TRACK : %s" % await device.track_info()) print("STATE : %s" % await device.transport_state()) await device.skip(2) time.sleep(0.5) print("TRACK : %s" % await device.track_info()) print("STATE : %s" % await device.transport_state()) await device.stop() await device.set_source(0) await device.set_source(1) print("SOURCE : %s" % await device.source()) await device.set_standby(True) print("STANDBY : %s" % await device.is_in_standby()) print("----") loop = asyncio.get_event_loop() loop.run_until_complete(main()) bazwilliams-openhomedevice-d96c7b5/openhomedevice/000077500000000000000000000000001465544220200224435ustar00rootroot00000000000000bazwilliams-openhomedevice-d96c7b5/openhomedevice/TODO000066400000000000000000000005101465544220200231270ustar00rootroot00000000000000TODO: [X] Test older Volume Services [X] Test older Product Services [X] Reset Stubs [X] Refactor [X] Support play/skip/pause for software devices on Playlist [X] Support play/pause for software devices on Radio [X] Consider play media for software [X] Do not play_media if no radio service [X] Test with _real_ software playerbazwilliams-openhomedevice-d96c7b5/openhomedevice/__init__.py000066400000000000000000000000531465544220200245520ustar00rootroot00000000000000# path for imports name = "openhomedevice" bazwilliams-openhomedevice-d96c7b5/openhomedevice/device.py000066400000000000000000000244501465544220200242610ustar00rootroot00000000000000import json import asyncio from async_upnp_client.client_factory import UpnpFactory from async_upnp_client.aiohttp import AiohttpRequester # from async_upnp_client.aiohttp import AiohttpNotifyServer import openhomedevice.didl_lite as didl_lite import xml.etree.ElementTree as etree # def on_event(service, service_variables): # """Handle a UPnP event.""" # print( # "State variable change for %s, variables: %s", # service, # ",".join([sv.name for sv in service_variables]), # ) # obj = { # "service_id": service.service_id, # "service_type": service.service_type, # "state_variables": {sv.name: sv.value for sv in service_variables}, # } # print(json.dumps(obj)) class Device(object): def __init__(self, location): self.location = location def setup_services(self): self.product_service = self.device.service_id( "urn:av-openhome-org:serviceId:Product" ) self.volume_service = self.device.service_id( "urn:av-openhome-org:serviceId:Volume" ) self.transport_service = self.device.service_id( "urn:av-openhome-org:serviceId:Transport" ) self.playlist_service = self.device.service_id( "urn:av-openhome-org:serviceId:Playlist" ) self.info_service = self.device.service_id("urn:av-openhome-org:serviceId:Info") self.pins_service = self.device.service_id("urn:av-openhome-org:serviceId:Pins") self.radio_service = self.device.service_id( "urn:av-openhome-org:serviceId:Radio" ) self.update_service = self.device.service_id("urn:linn-co-uk:serviceId:Update") async def init(self): requester = AiohttpRequester() factory = UpnpFactory(requester) self.device = await factory.async_create_device(self.location) self.setup_services() # async def subscribe(self, service): # service.on_event = on_event # await self.server.event_handler.async_subscribe(service) # async def setup_subscriptions(self): # self.server = AiohttpNotifyServer(self.device.requester, 41234) # await self.server.start_server() # print("Listening on: %s", self.server.callback_url) # await self.subscribe(self.product_service) # await self.subscribe(self.volume_service) # await self.subscribe(self.transport_service) # await self.subscribe(self.info_service) # while True: # await asyncio.sleep(120) # await self.server.event_handler.async_resubscribe_all() def uuid(self): return self.device.udn def manufacturer(self): return self.device.manufacturer def model_name(self): return self.device.model_name def friendly_name(self): return self.device.friendly_name async def name(self): action = self.product_service.action("Product") return (await action.async_call())["Name"] async def room(self): action = self.product_service.action("Product") return (await action.async_call())["Room"] async def set_standby(self, standby_requested): await self.product_service.action("SetStandby").async_call( Value=standby_requested ) async def is_in_standby(self): action = self.product_service.action("Standby") return (await action.async_call())["Value"] async def transport_state(self): if self.transport_service: action = self.transport_service.action("TransportState") return (await action.async_call()).get("State") if (await self.source())["type"] == "Radio": action = self.radio_service.action("TransportState") return (await action.async_call()).get("Value") action = self.playlist_service.action("TransportState") return (await action.async_call()).get("Value") async def play(self): if self.transport_service: await self.transport_service.action("Play").async_call() else: if (await self.source())["type"] == "Radio": await self.radio_service.action("Play").async_call() else: await self.playlist_service.action("Play").async_call() async def play_media(self, track_details): if self.radio_service and track_details: set_channel_action = self.radio_service.action("SetChannel") uri = track_details.get("uri", "") await set_channel_action.async_call( Uri=uri, Metadata=didl_lite.generate_string(track_details) ) await self.radio_service.action("Play").async_call() async def stop(self): if self.transport_service: await self.transport_service.action("Stop").async_call() else: if (await self.source())["type"] == "Radio": await self.radio_service.action("Stop").async_call() else: await self.playlist_service.action("Stop").async_call() async def pause(self): if self.transport_service: await self.transport_service.action("Pause").async_call() else: if (await self.source())["type"] == "Radio": await self.radio_service.action("Pause").async_call() else: await self.playlist_service.action("Pause").async_call() async def skip(self, offset): action = None if self.transport_service: action = ( self.transport_service.action("SkipNext") if offset > 0 else self.transport_service.action("SkipPrevious") ) else: if (await self.source())["type"] == "Playlist": action = ( self.playlist_service.action("Next") if offset > 0 else self.playlist_service.action("Previous") ) if action: for x in range(0, abs(offset)): await action.async_call() async def source(self): index_action = self.product_service.action("SourceIndex") source_index = (await index_action.async_call())["Value"] source_action = self.product_service.action("Source") source_result = await source_action.async_call(Index=source_index) return {"type": source_result["Type"], "name": source_result["Name"]} @property def volume_enabled(self): return self.volume_service is not None async def volume(self): if not self.volume_enabled: return None action = self.volume_service.action("Volume") return (await action.async_call())["Value"] async def is_muted(self): if not self.volume_enabled: return None action = self.volume_service.action("Mute") result = await action.async_call() return result["Value"] async def set_volume(self, volume_level): if self.volume_enabled: action = self.volume_service.action("SetVolume") await action.async_call(Value=volume_level) async def increase_volume(self): if self.volume_enabled: await self.volume_service.action("VolumeInc").async_call() async def decrease_volume(self): if self.volume_enabled: await self.volume_service.action("VolumeDec").async_call() async def set_mute(self, mute_requested): if self.volume_enabled: await self.volume_service.action("SetMute").async_call(Value=mute_requested) async def set_source(self, index): await self.product_service.action("SetSourceIndex").async_call(Value=index) async def sources(self): action = self.product_service.action("SourceXml") result = await action.async_call() sources_list_xml = etree.fromstring(result["Value"]) sources = [] index = 0 for source_xml in sources_list_xml: visible = source_xml.find("Visible").text == "true" if visible: sources.append( { "index": index, "name": source_xml.find("Name").text, "type": source_xml.find("Type").text, } ) index = index + 1 return sources async def track_info(self): action = self.info_service.action("Track") result = await action.async_call() return didl_lite.parse(result["Metadata"]) @property def pins_enabled(self): return self.device.has_service("urn:av-openhome-org:service:Pins:1") async def _get_pin_id_array(self): action = self.pins_service.action("GetIdArray") result = await action.async_call() return json.loads(result["IdArray"]) async def _pin_metadata(self, ids): action = self.pins_service.action("ReadList") result = await action.async_call(Ids=json.dumps(ids)) return json.loads(result["List"]) async def pins(self): if not self.pins_enabled: return [] action = self.pins_service.action("GetDeviceMax") max_pins = (await action.async_call())["DeviceMax"] pin_id_array = await self._get_pin_id_array() pin_metadata = await self._pin_metadata(pin_id_array) pins = list() for i in range(max_pins): if pin_metadata[i].get("id") > 0: pin = { "index": i + 1, "title": pin_metadata[i].get("title"), "artworkUri": pin_metadata[i].get("artworkUri"), } pins.append(pin) return pins async def invoke_pin(self, pin_id): if self.pins_enabled: await self.pins_service.action("InvokeIndex").async_call(Index=(pin_id - 1)) async def software_status(self): if self.update_service: action = self.update_service.action("GetSoftwareStatus") result = await action.async_call() return json.loads(result["SoftwareStatus"]) async def check_latest_firmware(self): if self.update_service: action = await self.update_service.action("CheckNow").async_call() async def update_firmware(self): if self.update_service: await self.update_service.action("Apply").async_call() bazwilliams-openhomedevice-d96c7b5/openhomedevice/didl_lite.py000066400000000000000000000144041465544220200247510ustar00rootroot00000000000000import re import xml.etree.ElementTree as etree def generate_string(track_details): title = track_details.get("title", "") or "" uri = track_details.get("uri", "") or "" albumArtwork = track_details.get("albumArtwork", "") or "" return ( '' '' "{0}" '{1}' "{2}" "object.item.audioItem" "" "".format(title, uri, albumArtwork) ) def parse(metadata): track_details = {} if metadata is None: return track_details try: et = etree.fromstring(metadata).find( "DIDL-Lite:item", {"DIDL-Lite": "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"}, ) except: return track_details if et is None: return track_details track_details["type"] = find_element_value(et, "upnp:class", False) track_details["title"] = find_element_value(et, "dc:title", False) track_details["uri"] = find_element_value(et, "DIDL-Lite:res", False) track_details["artist"] = find_element_value(et, "upnp:artist", True) track_details["composer"] = find_element_value( et, "upnp:artist[@role='Composer']", True ) track_details["narrator"] = find_element_value( et, "upnp:artist[@role='Narrator']", True ) track_details["performer"] = find_element_value( et, "upnp:artist[@role='Performer']", True ) track_details["conductor"] = find_element_value( et, "upnp:artist[@role='Conductor']", True ) track_details["albumArtist"] = find_element_value( et, "upnp:artist[@role='AlbumArtist']", True ) track_details["genre"] = find_element_value(et, "upnp:genre", True) track_details["albumGenre"] = find_element_value(et, "upnp:genre", True) track_details["albumTitle"] = find_element_value(et, "upnp:album", False) track_details["albumArtwork"] = find_element_value(et, "upnp:albumArtURI", False) track_details["artwork"] = find_element_value(et, "upnp:artworkURI", False) track_details["year"] = parse_int(find_element_value(et, "dc:date", False)) track_details["disc"] = parse_int( find_element_value(et, "upnp:originalDiscNumber", False) ) track_details["discs"] = parse_int( find_element_value(et, "upnp:originalDiscCount", False) ) track_details["track"] = parse_int( find_element_value(et, "upnp:originalTrackNumber", False) ) track_details["tracks"] = parse_int( find_element_value(et, "upnp:originalTrackCount", False) ) track_details["author"] = find_element_value(et, "dc:author", True) track_details["publisher"] = find_element_value(et, "dc:publisher", False) track_details["published"] = find_element_value(et, "dc:published", False) track_details["description"] = find_element_value(et, "dc:description", False) track_details["rating"] = find_element_value(et, "upnp:rating", False) track_details["channels"] = parse_int( find_element_attribute_value(et, "DIDL-Lite:res", "nrAudioChannels") ) track_details["bitDepth"] = parse_int( find_element_attribute_value(et, "DIDL-Lite:res", "bitsPerSample") ) track_details["sampleRate"] = parse_int( find_element_attribute_value(et, "DIDL-Lite:res", "sampleFrequency") ) track_details["bitRate"] = parse_int( find_element_attribute_value(et, "DIDL-Lite:res", "bitrate") ) track_details["duration"] = parse_duration( find_element_attribute_value(et, "DIDL-Lite:res", "duration") ) track_details["mimeType"] = find_element_attribute_value( et, "DIDL-Lite:res", "protocolInfo" ) # "provider": "{provider}" // oh:provider # "work": "{work}" // oh:work # "movement": "{movement}" // oh:movement # "show": "{show}" // oh:show # "episode": {episode} // oh:episodeNumber # "episodes": {episodes} // oh:episodeCount # "website": "{website}" // oh:website # "location": "{location}" // oh:location // ISO 6709 # "details": "{details}" // oh:details # "extensions": "{extensions}" // oh:extensions // stringified json return track_details def parse_duration(value): if value == None: return None intFinder = re.compile(r"\d+") numbers = intFinder.findall(value.split(".")[0]) if len(numbers) == 3: return int(numbers[2]) + (int(numbers[1]) * 60) + (int(numbers[0]) * 360) if len(numbers) == 2: return int(numbers[1]) + (int(numbers[0]) * 60) if len(numbers) == 1: return int(numbers[0]) return None def parse_int(value): if value == None: return None intFinder = re.compile(r"\d+") numbers = intFinder.findall(value) if len(numbers) > 0: return int(numbers[0]) return None def find_element_attribute_value(et, itemKey, itemAttribute): namespaces = { "DIDL-Lite": "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/", "upnp": "urn:schemas-upnp-org:metadata-1-0/upnp/", "dc": "http://purl.org/dc/elements/1.1/", } value = et.find(itemKey, namespaces) parsedValue = None if value != None: attribute = value.attrib.get(itemAttribute, None) if attribute != None: parsedValue = attribute return parsedValue def find_element_value(et, itemKey, isArray): namespaces = { "DIDL-Lite": "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/", "upnp": "urn:schemas-upnp-org:metadata-1-0/upnp/", "dc": "http://purl.org/dc/elements/1.1/", } items = et.findall(itemKey, namespaces) parsedValue = None if len(items) > 0: parsedValue = items[0].text if isArray: values = set() array = [] if len(items) > 0: for i in items: value = i.text if value and value not in values: values.add(i.text) array.append(i.text) return list(array) else: return parsedValue bazwilliams-openhomedevice-d96c7b5/setup.cfg000066400000000000000000000000471465544220200212730ustar00rootroot00000000000000[metadata] description-file = README.mdbazwilliams-openhomedevice-d96c7b5/setup.py000066400000000000000000000016241465544220200211660ustar00rootroot00000000000000import setuptools with open("README.md", "r") as fh: long_description = fh.read() setuptools.setup( name = 'openhomedevice', version = '2.2.1', author = 'Barry John Williams', author_email = 'barry@bjw.me.uk', description='Provides an API for requesting information from an Openhome device', long_description=long_description, long_description_content_type="text/markdown", url='https://github.com/bazwilliams/openhomedevice', packages=setuptools.find_packages(), download_url = 'https://github.com/bazwilliams/openhomedevice/tarball/2.1', keywords = ['upnp', 'dlna', 'openhome', 'linn', 'ds', 'music', 'render', 'async'], install_requires = ['async_upnp_client>=0.40', 'lxml>=4.8.0'], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], python_requires='>=3.6' ) bazwilliams-openhomedevice-d96c7b5/tests/000077500000000000000000000000001465544220200206135ustar00rootroot00000000000000bazwilliams-openhomedevice-d96c7b5/tests/DeviceNoPinsTest.py000066400000000000000000000161211465544220200243540ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithNoPinsTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/nopinsdevice.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Pins-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-ExaktInputs-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() @async_test async def test_pins(self): result = await self.sut.pins() self.assertListEqual(result, list()) @async_test async def test_invoke_pin(self): await self.sut.invoke_pin(42) bazwilliams-openhomedevice-d96c7b5/tests/DeviceNoRadioTest.py000066400000000000000000000041331465544220200245010ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithNoRadioServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v1description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() @async_test async def test_play_media(self): track_details = { "uri": "https://host/uri.flac", "title": "TITLE", "albumArtwork": "https://host/uri.jpg", } await self.sut.play_media(track_details) bazwilliams-openhomedevice-d96c7b5/tests/DeviceNoVolumeTest.py000066400000000000000000000171551465544220200247220ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithNoVolumeTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/novolumedevice.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Pins-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-ExaktInputs-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_volume_enabled(self): self.assertFalse(self.sut.volume_enabled) @async_test async def test_volume_level(self): self.assertIsNone(await self.sut.volume()) @async_test async def test_volume_muted(self): self.assertIsNone(await self.sut.is_muted()) @async_test async def test_set_volume(self): self.assertIsNone(await self.sut.set_volume(11)) @async_test async def test_increase_volume(self): self.assertIsNone(await self.sut.increase_volume()) @async_test async def test_decrease_volume(self): self.assertIsNone(await self.sut.decrease_volume()) @async_test async def test_set_mute(self): self.assertIsNone(await self.sut.set_mute(True)) bazwilliams-openhomedevice-d96c7b5/tests/DeviceProduct1Test.py000066400000000000000000000036661465544220200246610ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithProduct1ServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v1description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_product_service_exists(self): self.assertIsNotNone(self.sut.product_service) bazwilliams-openhomedevice-d96c7b5/tests/DeviceProduct2Test.py000066400000000000000000000036661465544220200246620ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithProduct2ServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v2description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_product_service_exists(self): self.assertIsNotNone(self.sut.product_service) bazwilliams-openhomedevice-d96c7b5/tests/DeviceVolume1Test.py000066400000000000000000000036471465544220200245070ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithVolume1ServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v1description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_volume_enabled(self): self.assertTrue(self.sut.volume_enabled) bazwilliams-openhomedevice-d96c7b5/tests/DeviceVolume2Test.py000066400000000000000000000036471465544220200245100ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithVolume2ServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v2description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_volume_enabled(self): self.assertTrue(self.sut.volume_enabled) bazwilliams-openhomedevice-d96c7b5/tests/DeviceVolume3Test.py000066400000000000000000000036471465544220200245110ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class DeviceWithVolume3ServiceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/v3description.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-3/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_volume_enabled(self): self.assertTrue(self.sut.volume_enabled) bazwilliams-openhomedevice-d96c7b5/tests/DidlLiteTest.py000066400000000000000000000126751465544220200235320ustar00rootroot00000000000000import unittest from openhomedevice.didl_lite import generate_string, parse, parse_duration, parse_int class DidlLiteTests(unittest.TestCase): def test_int_parsing(self): self.assertEqual(parse_duration("42"), 42) self.assertEqual(parse_duration("42.5"), 42) self.assertIsNone(parse_int("forty")) self.assertIsNone(parse_int(None)) def test_duration_parsing(self): self.assertEqual(parse_duration("0:07:40.000"), 460) self.assertEqual(parse_duration("1:00.000"), 60) self.assertEqual(parse_duration("42.000"), 42) self.assertEqual(parse_duration("2:0.5"), 120) self.assertIsNone(parse_duration("forty")) self.assertIsNone(parse_duration(None)) def test_parse_empty_didlite(self): result = parse(None) self.assertEqual(result, {}) def test_parse_corrupt_didlite(self): result = parse( 'object.item.audioItem' ) self.assertEqual(result, {}) def test_parse_didlite_missing_item(self): result = parse( '' ) self.assertEqual(result, {}) def test_parse_special_chars(self): result = parse( 'object.item.audioItem.musicTrackApostrophe'Apostrophe(')Frank Zappahttps://i.scdn.co/image/ab67616d0000b27385b05f4bb3c88cf252f96b68spotify://connect' ) self.assertEqual(result["title"], "Apostrophe'") def test_empty_track_details(self): track_details = {} result = generate_string(track_details) self.assertEqual( result, 'object.item.audioItem', ) def test_track_details_title_is_none(self): track_details = {} track_details["title"] = None result = generate_string(track_details) self.assertEqual( result, 'object.item.audioItem', ) def test_track_details_uri_is_none(self): track_details = {} track_details["uri"] = None result = generate_string(track_details) self.assertEqual( result, 'object.item.audioItem', ) def test_track_details_albumArtwork_is_none(self): track_details = {} track_details["albumArtwork"] = None result = generate_string(track_details) self.assertEqual( result, 'object.item.audioItem', ) def test_track_details(self): track_details = {} track_details["albumArtwork"] = "ALBUMARTWORK" track_details["title"] = "TITLE" track_details["uri"] = "URI" result = generate_string(track_details) self.assertEqual( result, 'TITLEURIALBUMARTWORKobject.item.audioItem', ) bazwilliams-openhomedevice-d96c7b5/tests/LinnDeviceTest.py000066400000000000000000000653771465544220200240670ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses from openhomedevice.didl_lite import generate_string def escape(str): str = str.replace("&", "&") return str def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class FakeAction: def __init__(self, response=None): self.was_called_times = 0 self.response = response async def async_call(self, **kwargs): self.arguments = kwargs self.was_called_times += 1 return self.response def pins_actions(): return { "GetDeviceMax": FakeAction({"DeviceMax": 6}), "GetIdArray": FakeAction( {"IdArray": "[1,2,0,3,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]"} ), "ReadList": FakeAction( { "List": '[{"id":1,"mode":"transport","type":"source","uri":"transport:\\/\\/source?udn=4c494e4e-0026-0f22-2963-01387403013f&id=HDMI2&version=1","title":"Sky","description":"","artworkUri":"external:///source?type=Hdmi&systemName=HDMI2","shuffle":false},{"id":2,"mode":"transport","type":"source","uri":"transport:\\/\\/source?udn=4c494e4e-0026-0f22-2963-01387403013f&id=HDMI1&version=1","title":"Playstation 4","description":"","artworkUri":"external:///source?type=Hdmi&systemName=HDMI1","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":3,"mode":"transport","type":"source","uri":"transport:\\/\\/source?udn=4c494e4e-0026-0f22-2963-01387403013f&id=HDMI3&version=1","title":"Fire Stick","description":"","artworkUri":"external:///source?type=Hdmi&systemName=HDMI3","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":4,"mode":"transport","type":"source","uri":"transport:\\/\\/source?udn=4c494e4e-0026-0f22-2963-01387403013f&id=Balanced&version=1","title":"LP12","description":"","artworkUri":"external:///source?type=Analog&systemName=Balanced","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false},{"id":0,"mode":"","type":"","uri":"","title":"","description":"","artworkUri":"","shuffle":false}]' } ), "InvokeIndex": FakeAction(), } def firmware_actions(): return { "GetSoftwareStatus": FakeAction({ "SoftwareStatus": '{"status": "on_latest", "current_software": {"version": "4.100.502", "topic": "main", "channel": "release"}}'}), "CheckNow": FakeAction(), "Apply": FakeAction() } def info_actions(): return { "Track": FakeAction( { "Uri": "uri=scd://192.168.1.38:38921", "Metadata": escape('http://192.168.1.38:9100/api/image/cc7cbb0b22dd07348d535f1c0db8278a?scale=fit&width=512&height=512&format=image%2FpngBeastie BoysBeastie BoysBeastie BoysAlternative Pop/RockAlternative RapAlternative/Indie RockPop/RockRapIll CommunicationAdam YauchMike DAd-Rock2009Sabotage16uri=scd://192.168.1.38:38921track'), } ) } def volume_actions(): return { "Volume": FakeAction({"Value": 11}), "Mute": FakeAction({"Value": True}), "SetVolume": FakeAction(), "VolumeInc": FakeAction(), "VolumeDec": FakeAction(), "SetMute": FakeAction(), } def radio_actions(): return {"SetChannel": FakeAction(), "Play": FakeAction()} def transport_actions(): return { "TransportState": FakeAction({"State": "Playing"}), "Play": FakeAction(), "Pause": FakeAction(), "Stop": FakeAction(), "SkipNext": FakeAction(), "SkipPrevious": FakeAction(), } def product_actions(): return { "Product": FakeAction({"Name": b"My Friendly Name", "Room": b"Bathroom"}), "SetStandby": FakeAction(), "Standby": FakeAction({"Value": True}), "SourceIndex": FakeAction({"Value": 3}), "Source": FakeAction({"Type": "Analog", "Name": "Front Aux"}), "SetSourceIndex": FakeAction(), "SourceXml": FakeAction( { "Value": "PlaylistPlaylisttruePlaylistRadioRadiofalseRadioUPnP AVUpnpAvfalseUPnP AVSongcastReceiverfalseSongcastNet AuxNetAuxfalseNet AuxSpotifySpotifyfalseSpotifyRoonScdtrueRoonSpeakerTestPrivatefalseSpeakerTest" } ), } class FakeService: def __init__(self, actions): self.actions = actions def action(self, action_called): return self.actions[action_called] class LinnDeviceTests(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/linndescription.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-OAuth-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Pins-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-5/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-4/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-3/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-2/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_device_parses_uuid(self): self.assertEqual(self.sut.uuid(), "uuid:4c494e4e-1234-ab12-abcd-01234567819f") def test_device_parses_model_name(self): self.assertEqual(self.sut.model_name(), "My Model") def test_device_parses_manufacturer(self): self.assertEqual(self.sut.manufacturer(), "Leading Technology Firm") def test_device_parses_friendly_name(self): self.assertEqual(self.sut.friendly_name(), "Music Machine") @async_test async def test_device_name(self): self.sut.product_service = FakeService(product_actions()) self.assertEqual(await self.sut.name(), b"My Friendly Name") @async_test async def test_device_room(self): self.sut.product_service = FakeService(product_actions()) self.assertEqual(await self.sut.room(), b"Bathroom") @async_test async def test_set_standby_off(self): self.sut.product_service = FakeService(product_actions()) await self.sut.set_standby(False) self.assertFalse( self.sut.product_service.actions["SetStandby"].arguments["Value"] ) @async_test async def test_set_standby_on(self): self.sut.product_service = FakeService(product_actions()) await self.sut.set_standby(True) self.assertTrue( self.sut.product_service.actions["SetStandby"].arguments["Value"] ) @async_test async def test_is_in_standby(self): self.sut.product_service = FakeService(product_actions()) self.assertEqual(await self.sut.is_in_standby(), True) @async_test async def test_transport_state(self): self.sut.transport_service = FakeService(transport_actions()) self.assertEqual(await self.sut.transport_state(), "Playing") @async_test async def test_play(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.play() self.assertEqual(self.sut.transport_service.actions["Play"].was_called_times, 1) @async_test async def test_play_media_with_nothing(self): self.sut.radio_service = FakeService(radio_actions()) await self.sut.play_media(None) self.assertEqual( self.sut.radio_service.actions["SetChannel"].was_called_times, 0 ) self.assertEqual(self.sut.radio_service.actions["Play"].was_called_times, 0) @async_test async def test_play_media_invalid_url(self): self.sut.radio_service = FakeService(radio_actions()) track_details = {"title": "TITLE", "albumArtwork": "https://host/uri.jpg"} await self.sut.play_media(track_details) self.assertEqual( self.sut.radio_service.actions["SetChannel"].arguments["Uri"], "" ) self.assertEqual( self.sut.radio_service.actions["SetChannel"].arguments["Metadata"], generate_string(track_details), ) self.assertEqual(self.sut.radio_service.actions["Play"].was_called_times, 1) @async_test async def test_play_media(self): self.sut.radio_service = FakeService(radio_actions()) track_details = { "uri": "https://host/uri.flac", "title": "TITLE", "albumArtwork": "https://host/uri.jpg", } await self.sut.play_media(track_details) self.assertEqual( self.sut.radio_service.actions["SetChannel"].arguments["Uri"], "https://host/uri.flac", ) self.assertEqual( self.sut.radio_service.actions["SetChannel"].arguments["Metadata"], generate_string(track_details), ) self.assertEqual(self.sut.radio_service.actions["Play"].was_called_times, 1) @async_test async def test_stop(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.stop() self.assertEqual(self.sut.transport_service.actions["Stop"].was_called_times, 1) @async_test async def test_pause(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.pause() self.assertEqual( self.sut.transport_service.actions["Pause"].was_called_times, 1 ) @async_test async def test_skip_forward(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.skip(10) self.assertEqual( self.sut.transport_service.actions["SkipNext"].was_called_times, 10 ) @async_test async def test_skip_backwards(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.skip(-10) self.assertEqual( self.sut.transport_service.actions["SkipPrevious"].was_called_times, 10 ) @async_test async def test_skip_nowhere(self): self.sut.transport_service = FakeService(transport_actions()) await self.sut.skip(0) self.assertEqual( self.sut.transport_service.actions["SkipPrevious"].was_called_times, 0 ) self.assertEqual( self.sut.transport_service.actions["SkipNext"].was_called_times, 0 ) @async_test async def test_source(self): self.sut.product_service = FakeService(product_actions()) self.assertEqual( await self.sut.source(), {"type": "Analog", "name": "Front Aux"} ) self.assertEqual( self.sut.product_service.actions["Source"].arguments["Index"], 3 ) def test_volume_enabled(self): self.assertTrue(self.sut.volume_enabled) @async_test async def test_volume_level(self): self.sut.volume_service = FakeService(volume_actions()) self.assertEqual(await self.sut.volume(), 11) @async_test async def test_volume_muted(self): self.sut.volume_service = FakeService(volume_actions()) self.assertTrue(await self.sut.is_muted()) @async_test async def test_set_volume(self): self.sut.volume_service = FakeService(volume_actions()) await self.sut.set_volume(11) self.assertEqual( self.sut.volume_service.actions["SetVolume"].arguments["Value"], 11 ) @async_test async def test_increase_volume(self): self.sut.volume_service = FakeService(volume_actions()) await self.sut.increase_volume() self.assertEqual( self.sut.volume_service.actions["VolumeInc"].was_called_times, 1 ) @async_test async def test_decrease_volume(self): self.sut.volume_service = FakeService(volume_actions()) await self.sut.decrease_volume() self.assertEqual( self.sut.volume_service.actions["VolumeDec"].was_called_times, 1 ) @async_test async def test_set_mute(self): self.sut.volume_service = FakeService(volume_actions()) await self.sut.set_mute(True) self.assertTrue(self.sut.volume_service.actions["SetMute"].arguments["Value"]) @async_test async def test_set_source(self): self.sut.product_service = FakeService(product_actions()) await self.sut.set_source(4) self.assertEqual( self.sut.product_service.actions["SetSourceIndex"].arguments["Value"], 4 ) @async_test async def test_sources(self): self.sut.product_service = FakeService(product_actions()) self.assertEqual( await self.sut.sources(), [ {"index": 0, "name": "Playlist", "type": "Playlist"}, {"index": 6, "name": "Roon", "type": "Scd"}, ], ) @async_test async def test_track_info(self): self.sut.info_service = FakeService(info_actions()) self.assertEqual( await self.sut.track_info(), { "type": "track", "title": "Sabotage", "uri": "uri=scd://192.168.1.38:38921", "artist": ["Beastie Boys", "Adam Yauch", "Mike D", "Ad-Rock"], "composer": [], "narrator": [], "performer": [], "conductor": [], "albumArtist": ["Beastie Boys"], "genre": [ "Alternative Pop/Rock", "Alternative Rap", "Alternative/Indie Rock", "Pop/Rock", "Rap", ], "albumGenre": [ "Alternative Pop/Rock", "Alternative Rap", "Alternative/Indie Rock", "Pop/Rock", "Rap", ], "albumTitle": "Ill Communication", "albumArtwork": "http://192.168.1.38:9100/api/image/cc7cbb0b22dd07348d535f1c0db8278a?scale=fit&width=512&height=512&format=image%2Fpng", "artwork": None, "year": 2009, "disc": None, "discs": None, "track": 6, "tracks": None, "author": [], "publisher": None, "published": None, "description": None, "rating": None, "channels": None, "bitDepth": None, "sampleRate": None, "bitRate": None, "duration": 178, "mimeType": None, }, ) @async_test async def test_pins(self): self.sut.pins_service = FakeService(pins_actions()) self.assertListEqual( await self.sut.pins(), [ { "index": 1, "title": "Sky", "artworkUri": "external:///source?type=Hdmi&systemName=HDMI2", }, { "index": 2, "title": "Playstation 4", "artworkUri": "external:///source?type=Hdmi&systemName=HDMI1", }, { "index": 4, "title": "Fire Stick", "artworkUri": "external:///source?type=Hdmi&systemName=HDMI3", }, { "index": 6, "title": "LP12", "artworkUri": "external:///source?type=Analog&systemName=Balanced", }, ], ) self.assertEqual( self.sut.pins_service.actions["ReadList"].arguments["Ids"], "[1, 2, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", ) @async_test async def test_invoke_pin(self): self.sut.pins_service = FakeService(pins_actions()) await self.sut.invoke_pin(42) self.assertEqual( self.sut.pins_service.actions["InvokeIndex"].arguments["Index"], 41 ) @async_test async def test_check_software(self): self.sut.update_service = FakeService(firmware_actions()) result = await self.sut.software_status() self.assertEqual(result["status"], "on_latest") self.assertEqual(result["current_software"]["version"], "4.100.502") @async_test async def test_check_latest_firmware(self): self.sut.update_service = FakeService(firmware_actions()) await self.sut.check_latest_firmware() self.assertEqual( self.sut.update_service.actions["CheckNow"].was_called_times, 1) @async_test async def test_check_latest_firmware(self): self.sut.update_service = FakeService(firmware_actions()) await self.sut.update_firmware() self.assertEqual( self.sut.update_service.actions["Apply"].was_called_times, 1) bazwilliams-openhomedevice-d96c7b5/tests/OpenhomeDevicePlaylistTest.py000066400000000000000000000167641465544220200264570ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class FakeAction: def __init__(self, response=None): self.was_called_times = 0 self.response = response async def async_call(self, **kwargs): self.arguments = kwargs self.was_called_times += 1 return self.response def product_actions(): return { "Product": FakeAction({"Name": b"My Friendly Name", "Room": b"Bathroom"}), "SetStandby": FakeAction(), "Standby": FakeAction({"Value": True}), "SourceIndex": FakeAction({"Value": 3}), "Source": FakeAction({"Type": "Playlist", "Name": "Playlist"}), "SetSourceIndex": FakeAction(), "SourceXml": FakeAction( { "Value": "PlaylistPlaylisttruePlaylistRadioRadiofalseRadioUPnP AVUpnpAvfalseUPnP AVSongcastReceiverfalseSongcastNet AuxNetAuxfalseNet AuxSpotifySpotifyfalseSpotifyRoonScdtrueRoonSpeakerTestPrivatefalseSpeakerTest" } ), } def playlist_actions(): return { "TransportState": FakeAction({"Value": "Playing"}), "Play": FakeAction(), "Pause": FakeAction(), "Stop": FakeAction(), "Next": FakeAction(), "Previous": FakeAction(), } class FakeService: def __init__(self, actions): self.actions = actions def action(self, action_called): return self.actions[action_called] class OpenhomeDevicePlaylistTest(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/softwaredescription.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Product-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Volume-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Credentials-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Time-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Config-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Receiver-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Sender-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Radio-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_device_parses_uuid(self): self.assertEqual(self.sut.uuid(), "uuid:softplayer.local") @async_test async def test_transport_state(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.assertEqual(await self.sut.transport_state(), "Playing") @async_test async def test_play(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.play() self.assertEqual(self.sut.playlist_service.actions["Play"].was_called_times, 1) @async_test async def test_stop(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.stop() self.assertEqual(self.sut.playlist_service.actions["Stop"].was_called_times, 1) @async_test async def test_pause(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.pause() self.assertEqual(self.sut.playlist_service.actions["Pause"].was_called_times, 1) @async_test async def test_skip_forward(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.skip(10) self.assertEqual(self.sut.playlist_service.actions["Next"].was_called_times, 10) @async_test async def test_skip_backwards(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.skip(-10) self.assertEqual( self.sut.playlist_service.actions["Previous"].was_called_times, 10 ) @async_test async def test_skip_nowhere(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.skip(0) self.assertEqual( self.sut.playlist_service.actions["Previous"].was_called_times, 0 ) self.assertEqual(self.sut.playlist_service.actions["Next"].was_called_times, 0) bazwilliams-openhomedevice-d96c7b5/tests/OpenhomeDeviceRadioTest.py000066400000000000000000000171151465544220200257030ustar00rootroot00000000000000import unittest import os import asyncio from openhomedevice.device import Device from aioresponses import aioresponses def async_test(coro): def wrapper(*args, **kwargs): loop = asyncio.new_event_loop() try: return loop.run_until_complete(coro(*args, **kwargs)) finally: loop.close() return wrapper class FakeAction: def __init__(self, response=None): self.was_called_times = 0 self.response = response async def async_call(self, **kwargs): self.arguments = kwargs self.was_called_times += 1 return self.response def product_actions(): return { "Product": FakeAction({"Name": b"My Friendly Name", "Room": b"Bathroom"}), "SetStandby": FakeAction(), "Standby": FakeAction({"Value": True}), "SourceIndex": FakeAction({"Value": 3}), "Source": FakeAction({"Type": "Radio", "Name": "Radio"}), "SetSourceIndex": FakeAction(), "SourceXml": FakeAction( { "Value": "PlaylistPlaylisttruePlaylistRadioRadiofalseRadioUPnP AVUpnpAvfalseUPnP AVSongcastReceiverfalseSongcastNet AuxNetAuxfalseNet AuxSpotifySpotifyfalseSpotifyRoonScdtrueRoonSpeakerTestPrivatefalseSpeakerTest" } ), } def playlist_actions(): return { "TransportState": FakeAction({"Value": "Playing"}), "Play": FakeAction(), "Pause": FakeAction(), "Stop": FakeAction(), "Next": FakeAction(), "Previous": FakeAction(), } def radio_actions(): return { "TransportState": FakeAction({"Value": "Playing"}), "Play": FakeAction(), "Pause": FakeAction(), "Stop": FakeAction(), } class FakeService: def __init__(self, actions): self.actions = actions def action(self, action_called): return self.actions[action_called] class OpenhomeDevicePlaylistTest(unittest.TestCase): @async_test @aioresponses() async def setUp(self, mocked): LOCATION = "http://mydevice:12345/desc.xml" with open( os.path.join(os.path.dirname(__file__), "data/softwaredescription.xml") ) as file: mocked.get(LOCATION, body=file.read()) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Product-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Volume-2/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Credentials-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Time-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Info-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Config-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Playlist-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Receiver-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Sender-1/service.xml", body="", ) mocked.get( "http://mydevice:12345/softplayer.local/Upnp/av.openhome.org-Radio-1/service.xml", body="", ) self.sut = Device(LOCATION) await self.sut.init() soap_request_calls = [] return super().setUp() def test_device_parses_uuid(self): self.assertEqual(self.sut.uuid(), "uuid:softplayer.local") @async_test async def test_transport_state(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.sut.radio_service = FakeService(radio_actions()) self.assertEqual(await self.sut.transport_state(), "Playing") @async_test async def test_play(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.sut.radio_service = FakeService(radio_actions()) await self.sut.play() self.assertEqual(self.sut.radio_service.actions["Play"].was_called_times, 1) @async_test async def test_stop(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.sut.radio_service = FakeService(radio_actions()) await self.sut.stop() self.assertEqual(self.sut.radio_service.actions["Stop"].was_called_times, 1) @async_test async def test_pause(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.sut.radio_service = FakeService(radio_actions()) await self.sut.pause() self.assertEqual(self.sut.radio_service.actions["Pause"].was_called_times, 1) @async_test async def test_skip_forward_does_nothing(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) await self.sut.skip(10) self.assertEqual(self.sut.playlist_service.actions["Next"].was_called_times, 0) @async_test async def test_skip_backwards_does_nothing(self): self.sut.product_service = FakeService(product_actions()) self.sut.playlist_service = FakeService(playlist_actions()) self.sut.radio_service = FakeService(radio_actions()) await self.sut.skip(-10) self.assertEqual( self.sut.playlist_service.actions["Previous"].was_called_times, 0 ) bazwilliams-openhomedevice-d96c7b5/tests/TrackInfoParserTest.py000066400000000000000000000251731465544220200250720ustar00rootroot00000000000000from openhomedevice.didl_lite import parse import unittest class TrackInfoParserTests(unittest.TestCase): def setUp(self): self.sut = parse def test_one(self): element_string = 'object.item.audioItem.musicTrackViolin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba) : Violin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba): II. FugaFahmi AlqhaiFahmi AlqhaiFahmi Alqhai, Performer - Johann Sebastian Bach, ComposerGlossa http://static.qobuz.com/images/covers/58/20/8424562332058_600.jpghttp://static.qobuz.com/images/covers/58/20/8424562332058_230.jpgKlassiek2017-01-06Digital bookletThe Bach Album2000974e2-681e-1a36-ffff-ffffa38afd93http://192.168.0.110:58050/stream/audio/b362f0f7a1ff33b176bcf2adde75af96.flac' result = self.sut(element_string) self.assertEqual(result.get("type"), "object.item.audioItem.musicTrack") self.assertEqual( result.get("title"), "Violin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba) : Violin Sonata No. 2 in A Minor, BWV 1003 (Arr. for Viola da gamba): II. Fuga", ) self.assertEqual( result.get("uri"), "http://192.168.0.110:58050/stream/audio/b362f0f7a1ff33b176bcf2adde75af96.flac", ) self.assertSetEqual( set(result.get("artist")), set( [ "Fahmi Alqhai, Performer - Johann Sebastian Bach, Composer", "Fahmi Alqhai", ] ), ) self.assertSetEqual(set(result.get("conductor")), set()) self.assertSetEqual(set(result.get("albumArtist")), set()) self.assertSetEqual(set(result.get("genre")), set(["Klassiek"])) self.assertSetEqual(set(result.get("albumGenre")), set(["Klassiek"])) self.assertEqual(result.get("albumTitle"), "The Bach Album") self.assertEqual( result.get("albumArtwork"), "http://static.qobuz.com/images/covers/58/20/8424562332058_600.jpg", ) self.assertEqual(result.get("artwork"), None) self.assertEqual(result.get("year"), 2017) self.assertEqual(result.get("disc"), None) self.assertEqual(result.get("discs"), None) self.assertEqual(result.get("track"), 2) self.assertEqual(result.get("tracks"), None) self.assertSetEqual(set(result.get("author")), set()) self.assertEqual(result.get("publisher"), "Glossa") self.assertEqual(result.get("published"), None) self.assertEqual(result.get("description"), None) self.assertEqual(result.get("rating"), None) self.assertEqual(result.get("channels"), 2) self.assertEqual(result.get("bitDepth"), 16) self.assertEqual(result.get("sampleRate"), 44100) self.assertEqual(result.get("bitRate"), None) self.assertEqual(result.get("duration"), 460) self.assertEqual( result.get("mimeType"), "http-get:*:audio/x-flac:DLNA.ORG_OP=01;DLNA.ORG_FLAGS=01700000000000000000000000000000", ) def test_two(self): element_string = 'BBC Radio 6 Music (AAA)http://opml.radiotime.com/Tune.ashx?id=s44491&formats=mp3,wma,aac,ogg,hls&partnerId=ah2rjr68&username=bazwilliams&c=ebrowsehttp://cdn-radiotime-logos.tunein.com/s44491q.pngobject.item.audioItem' result = self.sut(element_string) self.assertEqual(result.get("type"), "object.item.audioItem") self.assertEqual(result.get("title"), "BBC Radio 6 Music (AAA)") self.assertEqual( result.get("uri"), "http://opml.radiotime.com/Tune.ashx?id=s44491&formats=mp3,wma,aac,ogg,hls&partnerId=ah2rjr68&username=bazwilliams&c=ebrowse", ) self.assertSetEqual(set(result.get("artist")), set()) self.assertSetEqual(set(result.get("conductor")), set()) self.assertSetEqual(set(result.get("albumArtist")), set()) self.assertSetEqual(set(result.get("genre")), set()) self.assertSetEqual(set(result.get("albumGenre")), set()) self.assertEqual(result.get("albumTitle"), None) self.assertEqual( result.get("albumArtwork"), "http://cdn-radiotime-logos.tunein.com/s44491q.png", ) self.assertEqual(result.get("artwork"), None) self.assertEqual(result.get("year"), None) self.assertEqual(result.get("disc"), None) self.assertEqual(result.get("discs"), None) self.assertEqual(result.get("track"), None) self.assertEqual(result.get("tracks"), None) self.assertSetEqual(set(result.get("author")), set()) self.assertEqual(result.get("publisher"), None) self.assertEqual(result.get("published"), None) self.assertEqual(result.get("description"), None) self.assertEqual(result.get("rating"), None) self.assertEqual(result.get("channels"), None) self.assertEqual(result.get("bitDepth"), None) self.assertEqual(result.get("sampleRate"), None) self.assertEqual(result.get("bitRate"), 40000) self.assertEqual(result.get("duration"), None) self.assertEqual(result.get("mimeType"), "*:*:*:*") def test_three(self): element_string = 'The Mystic Trumpeter, Op. 18 - Colin Matthews & Imogen Holstobject.item.audioItem.musicTrackhttp://192.168.1.126:9790/minimserver/*/music/Albums/RSNO*20-*20Holst*20-*20The*20Planets,*20The*20Mystic*20Trumpeter/09*20The*20Mystic*20Trumpeter,*20Op.*2018*20-*20Colin*20Matthews*20*26*20Imogen*20Holst.mp3/$!picture-633-144623.jpgHolst: The Planets, The Mystic TrumpeterRoyal Scottish National Orchestra; David Lloyd-JonesRoyal Scottish National Orchestra; David Lloyd-Jones2001-01-01ClassicalOrchestralhttp://192.168.1.126:9790/minimserver/*/music/Albums/RSNO*20-*20Holst*20-*20The*20Planets,*20The*20Mystic*20Trumpeter/09*20The*20Mystic*20Trumpeter,*20Op.*2018*20-*20Colin*20Matthews*20*26*20Imogen*20Holst.mp3' result = self.sut(element_string) self.assertEqual(result.get("type"), "object.item.audioItem.musicTrack") self.assertEqual( result.get("title"), "The Mystic Trumpeter, Op. 18 - Colin Matthews & Imogen Holst", ) self.assertEqual( result.get("uri"), "http://192.168.1.126:9790/minimserver/*/music/Albums/RSNO*20-*20Holst*20-*20The*20Planets,*20The*20Mystic*20Trumpeter/09*20The*20Mystic*20Trumpeter,*20Op.*2018*20-*20Colin*20Matthews*20*26*20Imogen*20Holst.mp3", ) self.assertSetEqual( set(result.get("artist")), set(["Royal Scottish National Orchestra; David Lloyd-Jones"]), ) self.assertSetEqual(set(result.get("conductor")), set()) self.assertSetEqual( set(result.get("albumArtist")), set(["Royal Scottish National Orchestra; David Lloyd-Jones"]), ) self.assertSetEqual(set(result.get("genre")), set(["Orchestral", "Classical"])) self.assertSetEqual( set(result.get("albumGenre")), set(["Orchestral", "Classical"]) ) self.assertEqual( result.get("albumTitle"), "Holst: The Planets, The Mystic Trumpeter" ) self.assertEqual( result.get("albumArtwork"), "http://192.168.1.126:9790/minimserver/*/music/Albums/RSNO*20-*20Holst*20-*20The*20Planets,*20The*20Mystic*20Trumpeter/09*20The*20Mystic*20Trumpeter,*20Op.*2018*20-*20Colin*20Matthews*20*26*20Imogen*20Holst.mp3/$!picture-633-144623.jpg", ) self.assertEqual(result.get("artwork"), None) self.assertEqual(result.get("year"), 2001) self.assertEqual(result.get("disc"), None) self.assertEqual(result.get("discs"), None) self.assertEqual(result.get("track"), None) self.assertEqual(result.get("tracks"), None) self.assertSetEqual(set(result.get("author")), set()) self.assertEqual(result.get("publisher"), None) self.assertEqual(result.get("published"), None) self.assertEqual(result.get("description"), None) self.assertEqual(result.get("rating"), None) self.assertEqual(result.get("channels"), None) self.assertEqual(result.get("bitDepth"), None) self.assertEqual(result.get("sampleRate"), 44100) self.assertEqual(result.get("bitRate"), 24000) self.assertEqual(result.get("duration"), None) self.assertEqual(result.get("mimeType"), None) bazwilliams-openhomedevice-d96c7b5/tests/data/000077500000000000000000000000001465544220200215245ustar00rootroot00000000000000bazwilliams-openhomedevice-d96c7b5/tests/data/linndescription.xml000066400000000000000000000311541465544220200254560ustar00rootroot00000000000000 1 0 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f image/png 120 50 32 http://10.0.0.1:55178/4c494e4e-1234-ab12-abcd-01234567819f/Upnp/resource/res/icons/Device.png urn:av-openhome-org:service:ConfigApp:1 urn:av-openhome-org:serviceId:ConfigApp /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/event urn:av-openhome-org:service:Product:4 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-4/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-4/event urn:av-openhome-org:service:Volume:4 urn:av-openhome-org:serviceId:Volume /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-4/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-4/event urn:av-openhome-org:service:Credentials:1 urn:av-openhome-org:serviceId:Credentials /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/event urn:av-openhome-org:service:OAuth:1 urn:av-openhome-org:serviceId:OAuth /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-OAuth-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-OAuth-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-OAuth-1/event urn:av-openhome-org:service:Time:1 urn:av-openhome-org:serviceId:Time /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Config:2 urn:av-openhome-org:serviceId:Config /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Config-2/control urn:av-openhome-org:service:Transport:1 urn:av-openhome-org:serviceId:Transport /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/event urn:av-openhome-org:service:Pins:1 urn:av-openhome-org:serviceId:Pins /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Pins-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Pins-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Pins-1/event urn:linn-co-uk:service:Diagnostics:1 urn:linn-co-uk:serviceId:Diagnostics /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/event urn:linn-co-uk:service:Volkano:1 urn:linn-co-uk:serviceId:Volkano /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Volkano-1/control urn:linn-co-uk:service:Privacy:1 urn:linn-co-uk:serviceId:Privacy /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/event urn:av-openhome-org:service:Exakt:5 urn:av-openhome-org:serviceId:Exakt /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-5/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-5/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-5/event urn:linn-co-uk:service:Configuration:1 urn:linn-co-uk:serviceId:Configuration /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/event urn:linn-co-uk:service:Exakt2:4 urn:linn-co-uk:serviceId:Exakt2 /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-4/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-4/event urn:linn-co-uk:service:Update:3 urn:linn-co-uk:serviceId:Update /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-3/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-3/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-3/event urn:linn-co-uk:service:Cloud:2 urn:linn-co-uk:serviceId:Cloud /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-2/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-2/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/event urn:av-openhome-org:service:Radio:2 urn:av-openhome-org:serviceId:Radio /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-2/event urn:av-openhome-org:service:Receiver:1 urn:av-openhome-org:serviceId:Receiver /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/event urn:av-openhome-org:service:Sender:2 urn:av-openhome-org:serviceId:Sender /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/event urn:linn-co-uk:service:LipSync:1 urn:linn-co-uk:serviceId:LipSync /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/event urn:av-openhome-org:service:Debug:2 urn:av-openhome-org:serviceId:Debug /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Debug-2/control http://10.0.0.1:8080/Ds/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/nopinsdevice.xml000066400000000000000000000306451465544220200247440ustar00rootroot00000000000000 1 1 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f urn:av-openhome-org:service:ConfigApp:1 urn:av-openhome-org:serviceId:ConfigApp /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/event urn:av-openhome-org:service:Product:3 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/event urn:av-openhome-org:service:Volume:4 urn:av-openhome-org:serviceId:Volume /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-4/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-4/event urn:av-openhome-org:service:Credentials:1 urn:av-openhome-org:serviceId:Credentials /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/event urn:av-openhome-org:service:Time:1 urn:av-openhome-org:serviceId:Time /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Config:2 urn:av-openhome-org:serviceId:Config /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Config-2/control urn:av-openhome-org:service:Transport:1 urn:av-openhome-org:serviceId:Transport /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/event urn:linn-co-uk:service:Update:2 urn:linn-co-uk:serviceId:Update /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-2/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-2/event urn:linn-co-uk:service:Diagnostics:1 urn:linn-co-uk:serviceId:Diagnostics /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/event urn:linn-co-uk:service:Volkano:1 urn:linn-co-uk:serviceId:Volkano /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Volkano-1/control urn:linn-co-uk:service:Privacy:1 urn:linn-co-uk:serviceId:Privacy /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/event urn:av-openhome-org:service:Exakt:4 urn:av-openhome-org:serviceId:Exakt /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-4/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-4/event urn:linn-co-uk:service:Configuration:1 urn:linn-co-uk:serviceId:Configuration /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/event urn:linn-co-uk:service:Exakt2:1 urn:linn-co-uk:serviceId:Exakt2 /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-1/event urn:linn-co-uk:service:ExaktInputs:1 urn:linn-co-uk:serviceId:ExaktInputs /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-ExaktInputs-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-ExaktInputs-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-ExaktInputs-1/event urn:linn-co-uk:service:Cloud:1 urn:linn-co-uk:serviceId:Cloud /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-1/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/event urn:av-openhome-org:service:Radio:1 urn:av-openhome-org:serviceId:Radio /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-1/event urn:av-openhome-org:service:Receiver:1 urn:av-openhome-org:serviceId:Receiver /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/event urn:av-openhome-org:service:Sender:2 urn:av-openhome-org:serviceId:Sender /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/event urn:linn-co-uk:service:LipSync:1 urn:linn-co-uk:serviceId:LipSync /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/event urn:av-openhome-org:service:Debug:1 urn:av-openhome-org:serviceId:Debug /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Debug-1/control http://10.0.0.1:8080/Ds/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/novolumedevice.xml000066400000000000000000000306331465544220200252770ustar00rootroot00000000000000 1 1 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f urn:av-openhome-org:service:ConfigApp:1 urn:av-openhome-org:serviceId:ConfigApp /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-ConfigApp-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-ConfigApp-1/event urn:av-openhome-org:service:Product:3 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/event urn:av-openhome-org:service:Credentials:1 urn:av-openhome-org:serviceId:Credentials /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Credentials-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Credentials-1/event urn:av-openhome-org:service:Time:1 urn:av-openhome-org:serviceId:Time /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Time-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Time-1/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Config:2 urn:av-openhome-org:serviceId:Config /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Config-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Config-2/control urn:av-openhome-org:service:Transport:1 urn:av-openhome-org:serviceId:Transport /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Transport-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Transport-1/event urn:av-openhome-org:service:Pins:1 urn:av-openhome-org:serviceId:Pins /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Pins-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Pins-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Pins-1/event urn:linn-co-uk:service:Update:2 urn:linn-co-uk:serviceId:Update /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Update-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-2/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Update-2/event urn:linn-co-uk:service:Diagnostics:1 urn:linn-co-uk:serviceId:Diagnostics /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Diagnostics-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Diagnostics-1/event urn:linn-co-uk:service:Volkano:1 urn:linn-co-uk:serviceId:Volkano /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Volkano-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Volkano-1/control urn:linn-co-uk:service:Privacy:1 urn:linn-co-uk:serviceId:Privacy /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Privacy-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Privacy-1/event urn:av-openhome-org:service:Exakt:4 urn:av-openhome-org:serviceId:Exakt /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Exakt-4/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-4/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Exakt-4/event urn:linn-co-uk:service:Configuration:1 urn:linn-co-uk:serviceId:Configuration /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Configuration-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Configuration-1/event urn:linn-co-uk:service:Exakt2:1 urn:linn-co-uk:serviceId:Exakt2 /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Exakt2-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Exakt2-1/event urn:linn-co-uk:service:ExaktInputs:1 urn:linn-co-uk:serviceId:ExaktInputs /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-ExaktInputs-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-ExaktInputs-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-ExaktInputs-1/event urn:linn-co-uk:service:Cloud:1 urn:linn-co-uk:serviceId:Cloud /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-Cloud-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-Cloud-1/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Playlist-1/event urn:av-openhome-org:service:Radio:1 urn:av-openhome-org:serviceId:Radio /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Radio-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Radio-1/event urn:av-openhome-org:service:Receiver:1 urn:av-openhome-org:serviceId:Receiver /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Receiver-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Receiver-1/event urn:av-openhome-org:service:Sender:2 urn:av-openhome-org:serviceId:Sender /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Sender-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Sender-2/event urn:linn-co-uk:service:LipSync:1 urn:linn-co-uk:serviceId:LipSync /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/linn.co.uk-LipSync-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/control /4c494e4e-1234-ab12-abcd-01234567819f/linn.co.uk-LipSync-1/event urn:av-openhome-org:service:Debug:1 urn:av-openhome-org:serviceId:Debug /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Debug-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Debug-1/control http://10.0.0.1:8080/Ds/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/softwaredescription.xml000066400000000000000000000124231465544220200263460ustar00rootroot00000000000000 1 1 urn:av-openhome-org:device:Source:1 softplayer.local:SoftPlayer-OSX OpenHome ExampleMediaPlayer uuid:softplayer.local urn:av-openhome-org:service:Product:2 urn:av-openhome-org:serviceId:Product /softplayer.local/Upnp/av.openhome.org-Product-2/service.xml /softplayer.local/av.openhome.org-Product-2/control /softplayer.local/av.openhome.org-Product-2/event urn:av-openhome-org:service:Volume:2 urn:av-openhome-org:serviceId:Volume /softplayer.local/Upnp/av.openhome.org-Volume-2/service.xml /softplayer.local/av.openhome.org-Volume-2/control /softplayer.local/av.openhome.org-Volume-2/event urn:av-openhome-org:service:Credentials:1 urn:av-openhome-org:serviceId:Credentials /softplayer.local/Upnp/av.openhome.org-Credentials-1/service.xml /softplayer.local/av.openhome.org-Credentials-1/control /softplayer.local/av.openhome.org-Credentials-1/event urn:av-openhome-org:service:Time:1 urn:av-openhome-org:serviceId:Time /softplayer.local/Upnp/av.openhome.org-Time-1/service.xml /softplayer.local/av.openhome.org-Time-1/control /softplayer.local/av.openhome.org-Time-1/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /softplayer.local/Upnp/av.openhome.org-Info-1/service.xml /softplayer.local/av.openhome.org-Info-1/control /softplayer.local/av.openhome.org-Info-1/event urn:av-openhome-org:service:Config:1 urn:av-openhome-org:serviceId:Config /softplayer.local/Upnp/av.openhome.org-Config-1/service.xml /softplayer.local/av.openhome.org-Config-1/control urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /softplayer.local/Upnp/av.openhome.org-Playlist-1/service.xml /softplayer.local/av.openhome.org-Playlist-1/control /softplayer.local/av.openhome.org-Playlist-1/event urn:av-openhome-org:service:Receiver:1 urn:av-openhome-org:serviceId:Receiver /softplayer.local/Upnp/av.openhome.org-Receiver-1/service.xml /softplayer.local/av.openhome.org-Receiver-1/control /softplayer.local/av.openhome.org-Receiver-1/event urn:av-openhome-org:service:Sender:1 urn:av-openhome-org:serviceId:Sender /softplayer.local/Upnp/av.openhome.org-Sender-1/service.xml /softplayer.local/av.openhome.org-Sender-1/control /softplayer.local/av.openhome.org-Sender-1/event urn:av-openhome-org:service:Radio:1 urn:av-openhome-org:serviceId:Radio /softplayer.local/Upnp/av.openhome.org-Radio-1/service.xml /softplayer.local/av.openhome.org-Radio-1/control /softplayer.local/av.openhome.org-Radio-1/event http://192.168.1.32:58571/SoftPlayer/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/v1description.xml000066400000000000000000000053431465544220200250450ustar00rootroot00000000000000 1 1 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f urn:av-openhome-org:service:Product:1 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-1/event urn:av-openhome-org:service:Volume:1 urn:av-openhome-org:serviceId:Volume /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-1/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/event http://10.0.0.1:8080/Ds/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/v2description.xml000066400000000000000000000053431465544220200250460ustar00rootroot00000000000000 1 1 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f urn:av-openhome-org:service:Product:2 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-2/event urn:av-openhome-org:service:Volume:2 urn:av-openhome-org:serviceId:Volume /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-2/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-2/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-2/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/event http://10.0.0.1:8080/Ds/index.html bazwilliams-openhomedevice-d96c7b5/tests/data/v3description.xml000066400000000000000000000053431465544220200250470ustar00rootroot00000000000000 1 1 urn:linn-co-uk:device:Source:1 Music Machine Leading Technology Firm My Model uuid:4c494e4e-1234-ab12-abcd-01234567819f urn:av-openhome-org:service:Product:3 urn:av-openhome-org:serviceId:Product /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Product-3/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Product-3/event urn:av-openhome-org:service:Volume:3 urn:av-openhome-org:serviceId:Volume /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Volume-3/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-3/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Volume-3/event urn:av-openhome-org:service:Info:1 urn:av-openhome-org:serviceId:Info /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Info-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/control /4c494e4e-1234-ab12-abcd-01234567819f/av.openhome.org-Info-1/event urn:av-openhome-org:service:Playlist:1 urn:av-openhome-org:serviceId:Playlist /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/service.xml /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/control /4c494e4e-1234-ab12-abcd-01234567819f/Upnp/av.openhome.org-Playlist-1/event http://10.0.0.1:8080/Ds/index.html