Package: pykefcontrol
🔉 pykefcontrolPython library for controlling KEF speakers : LS50WII, LSX II and LS60⚠️Read the changelog to see breaking changes.For theasyncversion, please readthis section🏠️For the Home Assistant integration, please seehass-kef-connector🔉 pykefcontrol📄 General Informations⬇️ Installation⚙️ Usage👨‍💻 Get the IP address🎚️ Control the speaker with pykefcontrolFirst StepAvailable featuresAdvanced features🕵️ Specificity ofKefAsyncConnectorRenaming of property setters📜 Changelog📄 General InformationsThis library works with the KEF LS50 Wireless II, LSX II and LS60 only. If you are searching for a library for the first generation LS50W or LSX, you can useaiokef.
Pykefcontrol has 2 main components:KefConnectorandKefAsyncConnector. The first one can be used in all classic scripts and python programs, whereas the second one (KefAsyncConnector) can be used in asynchronous programs.⬇️ InstallationTo install pykefcontrol, you can use pip :pipinstallpykefcontrolYou can make sure you have the latest version by typing:>>> print(pykefcontrol.__version__)Currently, the latest version is version0.7.1⚙️ Usage👨‍💻 Get the IP addressTo use the pykefcontrol library, you need to know the IP address of your speakers. To do so, you can have a look at your router web page, or check in the KEF Connect app by doing the following :Launch the KEF Connect appTap the gear icon on the bottom rightThen your speaker name (It should be right below your profile information)Finally, the little circled "i" next to your speaker name in theMy SpeakerssectionYou should find your IP address in the "IP address" section under the formwww.xxx.yyy.zzz, wherewww,xxx,yyyandzzzare integers between0and255.🎚️ Control the speaker with pykefcontrolOnce pykefcontrol is installed and you have your KEF Speaker IP address, you can use pykefcontrol in the following way :First Step☝️For theasyncversion, please read the section🕵️ Specificity ofKefAsyncConnector.First, import the class and create aKefConnectorobject :frompykefcontrol.kef_connectorimportKefConnectormy_speaker=KefConnector("www.xxx.yyy.zzz")⚠️ Do not forget to replacewww.xxx.yyy.zzzwith your speaker IP address. You should give your IP address as a string. It's to say that you should leave the quotation marks"around the IP addressAvailable featuresOnce themy_speakerobject is created, you can use it in the following ways :Power, Shutdown and Status# Power onmy_speaker.power_on()# Shutdownmy_speaker.shutdown()# Get speaker status : it returns a string ('powerOn' or 'standby')my_speaker.status# it is not a method so it does not requires parenthesis# (output example) >>> 'powerOn'Speaker Info# Get the speaker MAC hardressmy_speaker.mac_address# (output example) >>> 'AB:CD:EF:12:13:45'# Get the speaker friendly name if configuredmy_speaker.speaker_name# (output example) >>> 'My Kef LS50W2'# Get the speaker modelmy_speaker.speaker_model# (output example) >>> 'LS50WII'# Get the firmware versionmy_speaker.firmware_version# (output example) >>> 'V27100'Source Control# Get currently selected source : it returns a string ('wifi', 'bluetooth', 'tv', 'optical', 'coaxial', 'analog')# If the speaker is not powered on, it will return 'standby'my_speaker.source# it is not a method so it does not requires parenthesis# (output example) >>> 'wifi'# Set the input source# If the speaker is shutdown, setting a source will power it onmy_speaker.source='wifi'# 'wifi is an example, you can use any other supported string 'tv', 'analog', etc..Control playback# Toggle play/pausemy_speaker.toggle_play_pause()# Next trackmy_speaker.next_track()# Previous trackmy_speaker.previous_track()Control volume# Muste speakermy_speaker.mute()# Unmute speakermy_speaker.unmute()# Get volume : it reruns an integer between 0 and 100my_speaker.volume# it is not a method so it does not require parenthesis# (output example) >>> 23# Set volumemy_speaker.volume=42# 42 for example but it can by any integer between 0 and 100.# Set volume (alternative way)my_speaker.set_volume(42)# 42 for example but it can by any integer between 0 and 100.Playback info# Check if the speaker is playing : it returns a boolean (either True or False)my_speaker.is_playing# it is not a method so it does not require parenthesis# (output example) >>> True# Get current media information : it retuns a dictionnary# (works on songs/podcast/radio. It may works on other media but I have not tested it yet)my_speaker.get_song_information()# (output example) >>> {'title':'Money','artist':'Pink Floyd','album':'The Dark Side of the Moon','cover_url':'http://cover.url'}# Get media length in miliseconds : it returns an integer representing the song length in msmy_speaker.song_length# it is not a method so it does not require parenthesis# (output example) >>> 300251# Get song progress : it returns the current playhead position in the current track in msmy_speaker.song_status# it is not a method so it does not require parenthesis# (output example) >>> 136900Information pollingPykefcontrol offers a polling functionality. Instead of manually fetching all parameters to see what has changed, you can use the methodpoll_speaker. This method returns the updated properties since the last time the changes were polled. If multiple changes are made to the same property, only the last change will be kept. It is technically possible to track all the changes to a property since the last poll, although it is not implemented. Please submit an issue if you need such a feature.poll_speakerwill return a dictionary whose keys are the names of the properties which have been updated.poll_speakerarguments:argumentrequireddefault valuecommenttimeoutOptional10timeoutis in seconds. If no change has been made since the last poll when you callpoll_speaker, the method will wait for changes duringtimeoutseconds for new changes. If there is a change before the end of the timeout,poll_speakerwill return them immediately and stop monitoring changes. If no changes are made, the method will return an empty dictionary.  ⚠️ the real timeout istimeout+ 0.5 seconds. The speaker will wait fortimeoutseconds before returning an empty dictionary if no changes are made. Therefore it is necessary to add a small margin in the python function to account for processing/networking time. Please submit an issue if you feel that this parameter needs tweaking.song_statusOptionalFalseDeprecated, please usepoll_song_statusinsteadpoll_song_statusOptionalFalseifpoll_song_statusif set toTrue, it will poll the song status (how many miliseconds of the current song have been played so far). If a song is playing andpoll_song_statusis set toTrue,poll_speakerwill return almost imediately sincesong_statusis updated at each second. This is forcing you to poll agressively to get other events. By default it is set toFalsein order to track other events more efficiently.my_speaker.poll_speaker(timeout=3)# example of a 3 seconds timeout# (output example) >>> {}  # it will return an empty dict if no changes were made# no suppose you start playing a songmy_speaker.poll_speaker(poll_song_satus=True)# timeout is 10 seconds by default# (output example) >>> {'song_info': {'title': 'Am I Wrong',#  'artist': 'Etienne de Crécy',#  'album': 'Am I Wrong',#  'cover_url': 'https://some-url/some-image.jpg'},# 'song_length': 238000,# 'status': 'playing',# 'song_status': 26085}## -> in this case it returns a dictionary with the keys "song_info" ,# "song_length", "status" and "song_status" containing information about the new song# now suppose you change the volume to 32my_speaker.poll_speaker(poll_song_satus=True)# (output example) >>> {'song_status': 175085, 'volume': 32}## -> in this case it returns both "song_status" (because the song kept playing),# and "volume" because you updated the volume# if you do not want to poll the song statusmy_speaker.poll_speaker()# (output example) >>> {'volume': 32}All the possible keys of the dictionary are:source,song_status,volume,song_info,song_length,status,speaker_status,device_name,muteandother.othercontains some of the speaker-specific information that might have changed, but are not properties of eitherKefConnectororKefAsyncConnector.Advanced featuresThis function is used internally by pykefcontrol and returns a JSON output with a lot of information. You might want to use them to get extra information such as the artwork/album cover URL, which does not have a dedicated functionyetin pykefcontrol.# Get currently playing media informationmy_speaker._get_player_data()# (output example) >>> {'trackRoles': {'icon': 'http://www.xxx.yyy.zzz:80/file/stream//tmp/temp_data_airPlayAlbum_xxxxxxxxx', 'title': 'I Want To Break Free', 'mediaData': {'resources': [{'duration': 263131}], 'metaData': {'album': 'Greatest Hits', 'artist': 'Queen'}}}, 'playId': {'systemMemberId': 'kef_one-xxxxxxxx', 'timestamp': 676181357}, 'mediaRoles': {'audioType': 'audioBroadcast', 'title': 'AirPlay', 'doNotTrack': True, 'type': 'audio', 'mediaData': {'resources': [{'mimeType': 'audio/unknown', 'uri': 'airplay://'}], 'metaData': {'serviceID': 'airplay', 'live': True, 'playLogicPath': 'airplay:playlogic'}}}, 'state': 'playing', 'status': {'duration': 263131, 'playSpeed': 1}, 'controls': {'pause': True, 'next_': True, 'previous': True}}🕵️ Specificity ofKefAsyncConnectorPykefcontrol offers anasynchronous connectorwith the same feature set as the synchronous connector. However, there are a few changes in the property setters. You can no longer usemy_speaker.volume = 28to set a property. You have to use the setter like so:await my_speaker.set_volume(28).The actions you make withKefAsyncConnectorshould be embedded in an async function. Here is a quick example :importasynciofrompykefcontrol.kef_connectorimportKefAsyncConnector# Define an async functionasyncdefmain():my_speaker=KefAsyncConnector("192.168.yyy.zz")# Get speaker nameprint(awaitmy_speaker.speaker_name)# Get volumeprint(awaitmy_speaker.volume)# Turn on speakerawaitmy_speaker.power_on()# Toggle play/pauseawaitmy_speaker.toggle_play_pause()# Set volume# Please read through to see precision about settersawaitmy_speaker.set_volume(28)# Set sourceawaitmy_speaker.set_source("bluetooth")# Set statusawaitmy_speaker.set_status("powerOn")# To avoid warning about an unclosed session# once your program is over, run:awaitmy_speaker._session.close()# This is not mandatory. But if you do not close# the session, you will have a warning.# Get looploop=asyncio.get_event_loop()# Run main function in async contextloop.run_until_complete(main())Renaming of property settersKefAsyncConnectorhas the same property and methods as its synchronous counterpartKefConnector. You can access the same properties and methods in an asynchronous context by usingawait my_speaker.propertyorawait my_speaker.method(). For the list of available properties and methods, readAvailable features.However, to have an asynchronous property setter, the way to set properties has changed. You should use the specific setter. For aproperty, the setter is calledset_property. As you can see in the example script above, to set the volume, useset_volume. Here is the list of properties with such setters :volume : useset_volumestate : useset_statesource : useset_source📜 Changelogversion 0.7.1Fix issue with async version ofget_speaker_modelandget_firmware_version.version 0.7Nowcompatible with LSX II and LS60!Addspeaker_modelandfirmware_versionproperties.⚠️song_statusargument ofpoll_speakeris now deprecated. Please usepoll_song_statusVersion 0.6.2modifypoll_speakerto prevent falling ifsong_statusis not properly defined by the speakerregenerate the queue_id ifsong_statuswas changed before the queue timeout.Version 0.6.1Add parametersong_statusto the methodpoll_speaker.Version 0.6Add methodpoll_speakerthat returns the last changes made to properties since the last poll.Version 0.5Add option to pass existingsession=aiohttp.ClientSession()toKefAsyncConnector.Add methodclose_sessionandresurect_sessionVersion 0.4AddKefAsyncConnector. A class with the same functionality asKefConnectorbut with async properties and methods.Version 0.3⚠️Breaking change :get_song_information()now returns a dictionary, no longer a tupleadd propertymac_addressthat returns the MAC address of the speaker as a stringadd propertyspeaker_namethat returns the friendly speaker name as defined in the KEF Connect app onboarding processVersion 0.2correct a bug inpower_onandshutdownVersion 0.1first version