avantrec / soco-cli Goto Github PK
View Code? Open in Web Editor NEWCommand Line Interface to Control Sonos Sound Systems
License: Apache License 2.0
Command Line Interface to Control Sonos Sound Systems
License: Apache License 2.0
Hi,
play_fav_radio_station_no don't always play the right radio:
sonos -l Salon favourite_radio_stations
1: Amazing Blues
2: France Inter 95.9 (Émissions-débats France)
3: K6 FM 101.6 (Musique Locale)
4: RTL 104.2 (Monde)
5: Radio Classique 105.8 (Classique)
6: Rire et Chansons 91.8 (Comédie)
7: SomaFM: Indie Pop Rocks!
8: franceinfo 101.2 (Émissions-débats France)
sonos -l Salon play_fav_radio_station_no 1 # play 1 - OK
sonos -l Salon play_fav_radio_station_no 3 # play 8 - NOK
play_fav_radio_station_no 1 -> play 1
play_fav_radio_station_no 2 -> play 2
play_fav_radio_station_no 3 -> play 8
play_fav_radio_station_no 4 -> play 3
play_fav_radio_station_no 5 -> play 5
play_fav_radio_station_no 6 -> play 6
play_fav_radio_station_no 7 -> play 4
play_fav_radio_station_no 8 -> play 7
Hi Avantrec, thank you for the soco-cli software.
Unfortunately I can't see how to add local linux file system mp3 files to either favourites or queues so I can then set them to play.
Can you tell me how this might be done please?
Kind regards
Martin
I use the following command to increase the volume:
sonos office volume $(($(sonos office volume) + 1))
This essentially gets the volume, increases the value with one, and uses that value to set the new volume. Since this uses two Sonos cli calls, it's pretty slow. Is there a better way to do this? Something like:
sonos office volume +1
Thanks!
Thanks for all the work you've been putting into improving this tool recently.
I believe this bug was introduced in 0184bb3.
The code that processes the result of run_command
is now:
if exit_code == 0:
print(output_msg)
else:
print(error_msg)
So if output_msg
is empty then a blank line is produced on stdout because print()
will always print a newline.
This may or may not affect error_msg
in a similar way.
Hey folks,
First of all thanks for the work you've put into this library.
I was wondering if it was possible to add soundcloud support to it. Afaics it should not be substantially different from how spotify works, right?
Any idea how to start or how difficult that would be to add?
Thanks and greetings,
Johannes
Hi,
I am hitting an edge case in your latest play_file
implementation.
In my case, always the new fallback with speaker reachability test is used, because my speakers are in a different vlan.
From time to time I need to play up to 5 files with short a duration (2-5 seconds) in a row.
If I do that I sometimes get SoCo-CLI: Macro: Exit code = 1 [Error: Can't determine an IP address for web server]
and the file is skipped.
I traced down the issue and the reason for this is that the function always tries to bind on PORT_END
but the port is not free at this time.
I am unsure if this is related that http_connection.close()
is never called, or the unbind of the port takes some time on os level.
I did a short PoC fix, and iterated through PORT_START until PORT_END to find a valid port to bind and the issue never came up again, even after aggressivly spamming the play_file command with 1 sec mp3 files.
Possible fix:
def find_valid_port() -> Optional[int]:
for port in range(PORT_START, PORT_END + 1):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('127.0.0.1', port))
return port
except socket.error:
continue
logging.error(
"Could not find a free port to bind"
)
return None
# If that fails, try to find a host IP address that can reach the target speaker
for adapter in adapters:
for ip in adapter.ips:
if ip.is_IPv4 and ip.ip != "127.0.0.1":
try:
logging.info(
"Checking target speaker's reachability from IP address '{}'"
.format(ip.ip)
)
port = find_valid_port()
if port is not None:
http_connection = http.client.HTTPConnection(
speaker.ip_address,
port=1400,
timeout=0.5,
source_address=(ip.ip, port),
)
http_connection.request("GET", "/status/info")
if http_connection.getresponse().status == 200:
return ip.ip
except:
continue
The new function find_valid_port(...)
could even be reused in def http_server(...)
Thanks in advance.
Hi,
I have a suggestion for a slight change in behaviour:
Often I will want to perform an action - say 'pause' on a particular speaker that I am listening to - say 'kitchen'.
So I run 'sonos -l kitchen pause', only to be told that kitchen is not the coordinator. I then have to figure out which speaker is the coordinator and run the same command on the correct speaker. I was wondering if your script could figure this out for me? I want to pause the music in the group that 'kitchen' is part of, but I don't really care who the coordinator is - I just want the music paused.
I'm not sure if there are disadvantges to this approach, but it seems it could be useful?
Richard
Quick one I hope. Is the all operator supported by the Web API? I have tried:
For example, but it returns "Speaker not found". Let me know, as it's a great shortcut to use in a "good night" automation, or "leaving house".
When I try to share the audio from my linux laptop using the Audio Sharing program, I get a notification that there is a device connecting to the audio stream but then the state of the speaker is STOPPED.
I've tried a couple of different ways of sending the stream:
play_uri "rtsp://192.168.xxx.xxx:8554/audio"
play_uri "x-rincon-mp3radio://192.168.xxx.xxx:8554/audio"
I've disabled firewalld and I'm getting the connected notification so I know networking isn't an issue.
There is no error that I can see so I'm a little stumped.
Hi,
I started playing around with soco_cli as I am looking for a way to stream youtube audio to my sonos speakers without requiring airplay on my computer. I have successfully managed to play demo mp3 and aac radio streams through the cli's play_uri method. Unfortunately, when trying to play aac encoded audio-only streams by urls taken from youtube-dl I get an error message on the respective host not being accessible.
One such URL is looking like this: https://rr4---sn-i5heen7s.googlevideo.com/videoplayback?expire=1666638001&ei=UIxWY_G1Kfzgx_APkueu-AU&ip=77.3.50.25&id=o-ANVFLH4TUmObAGEXWT00SzJtgJc2O9b0fJyE71QsOJoI&itag=140&source=youtube&requiressl=yes&mh=u4&mm=31%2C26&mn=sn-i5heen7s%2Csn-5goeen76&ms=au%2Conr&mv=m&mvi=4&pl=18&initcwndbps=1175000&vprv=1&mime=audio%2Fmp4&ns=FxlcKbwXSp9ERsyYHX45s1sI&gir=yes&clen=468901609&dur=28973.323&lmt=1653505664922520&mt=1666616255&fvip=2&keepalive=yes&fexp=24001373%2C24007246&c=WEB&txp=4531432&n=Hp0UHG_T1c0-cXOZE&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAJPJRlzT5in9psVC8L28dcQruOjg689OfP-X4Z-EpQ7HAiAa8drJ_CSZGizLXsfV7Txaj1NSzrbsXJD29YgjhDMaJQ%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRgIhAM8s-MbdJ2J9yZAFKSSbwoDiLEi4om5npFNyPMNdmrc6AiEA53Pgni4PrDcZCJuRObthpHzE7dluJ-e0vHDcoD8D2_g%3D
Expires this evening local time
Does anyone have a clue on how I might get my speakers to play the stream?
Thanks! ✌️
Hi,
I've just upgraded to version 0.4.26 and testing new 'sub_enabled' and 'surround_enabled'. The first of these works fine, but the second errors. I'm expecting both to give the answer as 'on'. Output from both commands with log set to info:
sonos --log info -l Lounge sub_enabled
2021-12-14 10:42:34,784 sonos.py:130 - main() - Setting up handlers for: [<Signals.SIGINT: 2>, <Signals.SIGTERM: 15>]
2021-12-14 10:42:34,784 sonos.py:237 - main() - No 'SPKR' environment variable set
2021-12-14 10:42:34,785 sonos.py:260 - main() - Found 1 action sequence(s): [['Lounge', 'sub_enabled']]
2021-12-14 10:42:34,785 utils.py:582 - get_speaker() - Using local speaker list
2021-12-14 10:42:34,785 match_speaker_names.py:18 - speaker_name_matches() - Found exact speaker name match for 'Lounge'
2021-12-14 10:42:34,786 sonos.py:470 - main() - Invoking 'run_command' with '<SoCo object at ip 192.168.0.233> sub_enabled ...'
2021-12-14 10:42:34,858 api.py:125 - run_command() - Return value: (0, 'on', '')
on
sonos --log info -l Lounge surround_enable`
2021-12-14 10:44:38,413 sonos.py:130 - main() - Setting up handlers for: [<Signals.SIGINT: 2>, <Signals.SIGTERM: 15>]
2021-12-14 10:44:38,413 sonos.py:237 - main() - No 'SPKR' environment variable set
2021-12-14 10:44:38,413 sonos.py:260 - main() - Found 1 action sequence(s): [['Lounge', 'surround_enabled']]
2021-12-14 10:44:38,414 utils.py:582 - get_speaker() - Using local speaker list
2021-12-14 10:44:38,414 match_speaker_names.py:18 - speaker_name_matches() - Found exact speaker name match for 'Lounge'
2021-12-14 10:44:38,415 sonos.py:470 - main() - Invoking 'run_command' with '<SoCo object at ip 192.168.0.233> surround_enabled ...'
2021-12-14 10:44:38,415 api.py:88 - run_command() - Exception: 'tuple' object has no attribute 'switch_to_coordinator'
2021-12-14 10:44:38,415 api.py:125 - run_command() - Return value: (1, '', "Error: 'tuple' object has no attribute 'switch_to_coordinator'")
Error: 'tuple' object has no attribute 'switch_to_coordinator'
sonos --version
soco-cli version: 0.4.26
soco version: 0.25.0
python version: 3.9.1
Thanks,
Richard
The README makes references to a "local library" as distinct from the "Sonos library". Is this something created on the local filesystem by the Sonos software?
I'm a Linux user, and thought (mistakenly, apparently) that it referred to my filesystem, but I get "not found" errors when I try to use local files as track and playlist names.
Greetings, pwt!
Is it possible to make sonos --actions
pipe-able?
For example, it would be nice to be able to do:
sonos --actions | grep "speaker"
or sonos --actions | tee actions.txt
For me, these commands produce the following error:
Traceback (most recent call last): File "/usr/local/bin/sonos", line 8, in <module> sys.exit(main()) File "/usr/local/lib/python3.9/site-packages/soco_cli/sonos.py", line 151, in main list_actions() File "/usr/local/lib/python3.9/site-packages/soco_cli/action_processor.py", line 2589, in list_actions items_per_line = get_terminal_size().columns // item_spacing OSError: [Errno 25] Inappropriate ioctl for device
I am guessing this is related to sub-processes created by the api and the inability to do i/o to/from the sub-process.
Session Info:
I am operating on an M1 macbook pro, but have installed soco-cli under an x86_64 brew installation.
soco-cli version: 0.4.46 soco version: 0.26.3 python version: 3.9.10 command path: /usr/local/bin/sonos
As always, many thanks.
Dear maintainers,
Thank you so much for this tool, it's really helpful and saves me time with my custom scripts :)
Before switching to this app,
A feature that I often used in my custom script is add_uri_to_queue, found in SoCo Github repo, docs here:
https://soco.readthedocs.io/en/latest/api/soco.core.html?highlight=_uri#soco.core.SoCo.add_uri_to_queue
I was unable to find this in the README.md however it does work and is in the action_processor.py
Could you document this feature as it really helps me with creating a daily URI playlist for Sonos and might help others as well.
Thank you!
Hi
I tried soco-cli in a raspberry.. worked perfectly
Not used to Python in windows, I have setup the environment and installed the soco-cli package..
First I navigate to the Python package repository on my machine..
cd C:\Users\Username\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages
From here, I can run
Python3 discovery.py
and it outputs my setup..
Sonos Household: Sonos__xxxxmyuid_
Room/Zone Name IP Address Device Model Visibility SW Version
---------------- -------------- -------------- ------------ ------------
Anton 192.168.xx.122 One SL Visible 14.0
Bureau 192.168.xx.108 Play:1 Visible 14.0
Kitchen 192.168.xx.170 One Visible 14.0
Living Room 192.168.xx.119 Play:1 Visible 14.0
Parents 192.168.xx.176 One Visible 14.0
1 Sonos Household(s) found
5 Sonos device(s) found
Saved speaker data at: C:\Users\Username/.soco-cli/speakers_v2.pickle
But then I try to run sonos SPEAKER play_file myfile.mp3 as I did on the raspbian..
python3 sonos.py Kitchen play_file myfile.mp3
Output
Traceback (most recent call last):
File "C:\Users\Username\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\LocalCache\local-packages\Python39\site-packages\soco_cli\sonos.py", line 36, in <module>
from .wait_actions import process_wait
ImportError: attempted relative import with no known parent package
Would you have any idea on how to turn this around ?
Thanks
Hi,
I am getting an error with the new audio_format action. Error is:
Error: 'SoCo' object has no attribute 'audio_input_format'
I think it's a simple typo at line 3259 of action_processor.py, which should be calling the soco function 'soundbar_audio_input_format' rather than 'audio_input_format'
For info my versions are:
sonos --version
soco-cli version: 0.4.28
soco version: 0.25.2
python version: 3.10.1
If you issue a sequential command in the interactive shell such as:
> wait_until 10:00 : line_in on
and then issue a CTRL-C to cancel the command, the wait_until
is cancelled, but the line_in on
will still execute. The desired behaviour is that the whole sequential command is cancelled.
Hi I was wondering if it is possible to basically replicate selecting the Sonos as an airplay speaker and playing the current Mac audio to it? Wanted to say auto move the current movie audio I'm watching on Netflix on my Mac over to my Sonos speakers with the press of a button on my stream deck. Anyway to do this?
Hi everyone,
I'm runnung two Sonos devices (two IKEA Symfonisk speakers: "Lounge" with IP address 192.168.2.213 and "Right" with IP address 192.168.2.194). Both speakers and the Ubuntu PC running soco-cli are on the same ethernet switch.
I can control them individually without problems using soco-cli commands (i.e. sonos and sonos-discover). Thanks for that :-)
As long as only one of the speakers is switched on, I can also use the room/zone name to control the active speaker.
As soon as I switch on both speakers, the soco-cli programs only discover one of the two speakers. It looks like they randomly choose the one that is found...
I would not care that much... but I would like to pair the two devices to build a stereo speaker. This only works with zone names but not with IP-addresses.
BTW: I also tried the same using the -n parameter (from -n 5 upto -n 20): no difference...
I could not find any issues on github w.r.t. soco or soco-cli that I could relate to the problem described.
Any ideas?
Below please find a sample session including a sonos-discover with debug output. Initially, the "Right" speaker was sitched off. I switched it on after executing the first command (i.e. sonos Lounge volume).
Thanks and all the best,
Thomas
drtjs@myhost:$ sonos Lounge volume$ sonos 192.168.2.213 volume
10
drtjs@myhost:
10
drtjs@myhost:$ sonos 192.168.2.194 volume$ sonos --version
10
drtjs@myhost:
soco-cli version: 0.4.52
soco version: 0.28.1
python version: 3.8.10
command path: /home/drtjs/.local/bin/sonos
drtjs@myhost:~$ sonos-discover
Sonos Household:
Room/Zone Name IP Address Device Model Visibility SW Version
Right 192.168.2.194 SYMFONISK Visible 9.3
1 Sonos Household(s) found
1 Sonos device(s) found
Saved speaker data at: /home/drtjs/.soco-cli/speakers_v2.pickle
drtjs@myhost:~$ sonos-discover
Sonos Household:
Room/Zone Name IP Address Device Model Visibility SW Version
Lounge 192.168.2.213 SYMFONISK Visible 9.3
1 Sonos Household(s) found
1 Sonos device(s) found
Saved speaker data at: /home/drtjs/.soco-cli/speakers_v2.pickle
drtjs@myhost:~$ sonos-discover
Sonos Household:
Room/Zone Name IP Address Device Model Visibility SW Version
Right 192.168.2.194 SYMFONISK Visible 9.3
1 Sonos Household(s) found
1 Sonos device(s) found
Saved speaker data at: /home/drtjs/.soco-cli/speakers_v2.pickle
drtjs@myhost:~$ sonos-discover
Sonos Household:
Room/Zone Name IP Address Device Model Visibility SW Version
Right 192.168.2.194 SYMFONISK Visible 9.3
1 Sonos Household(s) found
1 Sonos device(s) found
Saved speaker data at: /home/drtjs/.soco-cli/speakers_v2.pickle
drtjs@myhost:~$ sonos-discover --log=debug
2023-01-04 11:16:56,300 discovery.py:651 - _find_ipv4_networks() - Set of networks to search: {IPv4Network('192.168.2.0/24')}
2023-01-04 11:16:56,305 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.194:1400
2023-01-04 11:16:56,305 discovery.py:750 - _sonos_scan_worker_thread() - Found open port 1400 at IP '192.168.2.194'
2023-01-04 11:16:56,305 core.py:371 - init() - Created SoCo instance for ip: 192.168.2.194
2023-01-04 11:16:56,306 services.py:220 - getattr() - Dispatching method GetHouseholdID
2023-01-04 11:16:56,306 services.py:470 - send_command() - Request timeout set to 20.0
2023-01-04 11:16:56,310 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.194:1400
2023-01-04 11:16:56,312 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.213:1400
2023-01-04 11:16:56,312 discovery.py:750 - _sonos_scan_worker_thread() - Found open port 1400 at IP '192.168.2.213'
2023-01-04 11:16:56,312 core.py:371 - init() - Created SoCo instance for ip: 192.168.2.213
2023-01-04 11:16:56,312 services.py:220 - getattr() - Dispatching method GetHouseholdID
2023-01-04 11:16:56,312 services.py:470 - send_command() - Request timeout set to 20.0
2023-01-04 11:16:56,313 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.213:1400
2023-01-04 11:16:56,323 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.0:1400
2023-01-04 11:16:56,325 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.255:1400
2023-01-04 11:16:56,332 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.197:1400
2023-01-04 11:16:56,342 discovery.py:373 - scan_network() - Created 256 scanner threads
2023-01-04 11:16:56,393 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.200:1400
2023-01-04 11:16:56,393 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.1:1400
2023-01-04 11:16:56,394 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.8:1400
2023-01-04 11:16:56,395 connectionpool.py:428 - _make_request() - http://192.168.2.194:1400 "GET /xml/DeviceProperties1.xml HTTP/1.1" 200 19120
2023-01-04 11:16:56,396 connectionpool.py:428 - _make_request() - http://192.168.2.213:1400 "GET /xml/DeviceProperties1.xml HTTP/1.1" 200 19120
2023-01-04 11:16:56,397 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.203:1400
2023-01-04 11:16:56,397 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.204:1400
2023-01-04 11:16:56,397 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.128:1400
2023-01-04 11:16:56,398 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.2:1400
2023-01-04 11:16:56,401 services.py:483 - send_command() - Sending GetHouseholdID [] to 192.168.2.194
2023-01-04 11:16:56,402 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.6:1400
2023-01-04 11:16:56,404 services.py:483 - send_command() - Sending GetHouseholdID [] to 192.168.2.213
2023-01-04 11:16:56,413 services.py:484 - send_command() - Sending {'Content-Type': 'text/xml; charset="utf-8"', 'SOAPACTION': 'urn:schemas-upnp-org:service:DeviceProperties:1#GetHouseholdID'},
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetHouseholdID xmlns:u="urn:schemas-upnp-org:service:DeviceProperties:1"/>
</s:Body>
</s:Envelope>
2023-01-04 11:16:56,414 services.py:484 - send_command() - Sending {'Content-Type': 'text/xml; charset="utf-8"', 'SOAPACTION': 'urn:schemas-upnp-org:service:DeviceProperties:1#GetHouseholdID'},
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetHouseholdID xmlns:u="urn:schemas-upnp-org:service:DeviceProperties:1"/>
</s:Body>
</s:Envelope>
2023-01-04 11:16:56,417 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.213:1400
2023-01-04 11:16:56,419 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.194:1400
2023-01-04 11:16:56,424 connectionpool.py:428 - _make_request() - http://192.168.2.213:1400 "POST /DeviceProperties/Control HTTP/1.1" 200 306
2023-01-04 11:16:56,425 services.py:493 - send_command() - Received {'CONTENT-LENGTH': '306', 'CONTENT-TYPE': 'text/xml; charset="utf-8"', 'EXT': '', 'Server': 'Linux UPnP/1.0 Sonos/47.2-59120 (ZPS21)', 'Connection': 'close'}, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetHouseholdIDResponse xmlns:u="urn:schemas-upnp-org:service:DeviceProperties:1"></u:GetHouseholdIDResponse></s:Body></s:Envelope>
2023-01-04 11:16:56,425 services.py:495 - send_command() - Received status 200 from 192.168.2.213
2023-01-04 11:16:56,425 services.py:220 - getattr() - Dispatching method GetZoneGroupState
2023-01-04 11:16:56,425 services.py:470 - send_command() - Request timeout set to 20.0
2023-01-04 11:16:56,428 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.213:1400
2023-01-04 11:16:56,428 connectionpool.py:428 - _make_request() - http://192.168.2.194:1400 "POST /DeviceProperties/Control HTTP/1.1" 200 306
2023-01-04 11:16:56,429 services.py:493 - send_command() - Received {'CONTENT-LENGTH': '306', 'CONTENT-TYPE': 'text/xml; charset="utf-8"', 'EXT': '', 'Server': 'Linux UPnP/1.0 Sonos/47.2-59120 (ZPS21)', 'Connection': 'close'}, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetHouseholdIDResponse xmlns:u="urn:schemas-upnp-org:service:DeviceProperties:1"></u:GetHouseholdIDResponse></s:Body></s:Envelope>
2023-01-04 11:16:56,430 services.py:495 - send_command() - Received status 200 from 192.168.2.194
2023-01-04 11:16:56,430 services.py:220 - getattr() - Dispatching method GetZoneGroupState
2023-01-04 11:16:56,430 services.py:470 - send_command() - Request timeout set to 20.0
2023-01-04 11:16:56,432 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.194:1400
2023-01-04 11:16:56,437 connectionpool.py:428 - _make_request() - http://192.168.2.194:1400 "GET /xml/ZoneGroupTopology1.xml HTTP/1.1" 200 8276
2023-01-04 11:16:56,438 connectionpool.py:428 - _make_request() - http://192.168.2.213:1400 "GET /xml/ZoneGroupTopology1.xml HTTP/1.1" 200 8276
2023-01-04 11:16:56,440 services.py:483 - send_command() - Sending GetZoneGroupState [] to 192.168.2.194
2023-01-04 11:16:56,441 services.py:484 - send_command() - Sending {'Content-Type': 'text/xml; charset="utf-8"', 'SOAPACTION': 'urn:schemas-upnp-org:service:ZoneGroupTopology:1#GetZoneGroupState'},
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetZoneGroupState xmlns:u="urn:schemas-upnp-org:service:ZoneGroupTopology:1"/>
</s:Body>
</s:Envelope>
2023-01-04 11:16:56,443 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.194:1400
2023-01-04 11:16:56,445 services.py:483 - send_command() - Sending GetZoneGroupState [] to 192.168.2.213
2023-01-04 11:16:56,445 services.py:484 - send_command() - Sending {'Content-Type': 'text/xml; charset="utf-8"', 'SOAPACTION': 'urn:schemas-upnp-org:service:ZoneGroupTopology:1#GetZoneGroupState'},
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetZoneGroupState xmlns:u="urn:schemas-upnp-org:service:ZoneGroupTopology:1"/>
</s:Body>
</s:Envelope>
2023-01-04 11:16:56,447 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.213:1400
2023-01-04 11:16:56,451 connectionpool.py:428 - _make_request() - http://192.168.2.194:1400 "POST /ZoneGroupTopology/Control HTTP/1.1" 200 1254
2023-01-04 11:16:56,452 services.py:493 - send_command() - Received {'CONTENT-LENGTH': '1254', 'CONTENT-TYPE': 'text/xml; charset="utf-8"', 'EXT': '', 'Server': 'Linux UPnP/1.0 Sonos/47.2-59120 (ZPS21)', 'Connection': 'close'}, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetZoneGroupStateResponse xmlns:u="urn:schemas-upnp-org:service:ZoneGroupTopology:1"><ZoneGroups><ZoneGroup Coordinator="RINCON_347E5CF3316801400" ID="RINCON_347E5CF3316801400:3713604349"><ZoneGroupMember UUID="RINCON_347E5CF3316801400" Location="http://192.168.2.194:1400/xml/device_description.xml" ZoneName="Right" Icon="" Configuration="1" SoftwareVersion="47.2-59120" MinCompatibleVersion="46.0-00000" LegacyCompatibleVersion="36.0-00000" BootSeq="9" TVConfigurationError="0" HdmiCecAvailable="0" WirelessMode="0" WirelessLeafOnly="0" HasConfiguredSSID="0" ChannelFreq="2412" BehindWifiExtender="0" WifiEnabled="1" Orientation="0" RoomCalibrationState="4" SecureRegState="1" VoiceState="0" AirPlayEnabled="0" IdleState="1"/></ZoneGroup></ZoneGroups></u:GetZoneGroupStateResponse></s:Body></s:Envelope>
2023-01-04 11:16:56,452 services.py:495 - send_command() - Received status 200 from 192.168.2.194
2023-01-04 11:16:56,453 connectionpool.py:428 - _make_request() - http://192.168.2.213:1400 "POST /ZoneGroupTopology/Control HTTP/1.1" 200 1256
2023-01-04 11:16:56,454 services.py:493 - send_command() - Received {'CONTENT-LENGTH': '1256', 'CONTENT-TYPE': 'text/xml; charset="utf-8"', 'EXT': '', 'Server': 'Linux UPnP/1.0 Sonos/47.2-59120 (ZPS21)', 'Connection': 'close'}, <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetZoneGroupStateResponse xmlns:u="urn:schemas-upnp-org:service:ZoneGroupTopology:1"><ZoneGroups><ZoneGroup Coordinator="RINCON_347E5C3314FC01400" ID="RINCON_347E5C3314FC01400:3650021371"><ZoneGroupMember UUID="RINCON_347E5C3314FC01400" Location="http://192.168.2.213:1400/xml/device_description.xml" ZoneName="Lounge" Icon="" Configuration="1" SoftwareVersion="47.2-59120" MinCompatibleVersion="46.0-00000" LegacyCompatibleVersion="36.0-00000" BootSeq="26" TVConfigurationError="0" HdmiCecAvailable="0" WirelessMode="0" WirelessLeafOnly="0" HasConfiguredSSID="0" ChannelFreq="2412" BehindWifiExtender="0" WifiEnabled="1" Orientation="0" RoomCalibrationState="4" SecureRegState="1" VoiceState="0" AirPlayEnabled="0" IdleState="0"/></ZoneGroup></ZoneGroups></u:GetZoneGroupStateResponse></s:Body></s:Envelope>
2023-01-04 11:16:56,454 services.py:495 - send_command() - Received status 200 from 192.168.2.213
2023-01-04 11:16:56,455 zonegroupstate.py:176 - process_payload() - Updating ZGS with poll payload from 192.168.2.213 (1/2 processed)
2023-01-04 11:16:56,456 zonegroupstate.py:164 - update_cache() - Setting ZGS cache to 5s
2023-01-04 11:16:56,456 discovery.py:752 - _sonos_scan_worker_thread() - Confirmed Sonos device at IP '192.168.2.213'
2023-01-04 11:16:56,456 zonegroupstate.py:176 - process_payload() - Updating ZGS with poll payload from 192.168.2.194 (2/2 processed)
2023-01-04 11:16:56,457 zonegroupstate.py:164 - update_cache() - Setting ZGS cache to 5s
2023-01-04 11:16:56,457 discovery.py:752 - _sonos_scan_worker_thread() - Confirmed Sonos device at IP '192.168.2.194'
2023-01-04 11:16:56,510 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.223:1400
LOTS OF SCAN WORKER THREAD LINES DELETED w.r.t scanning ports on IP-addresses where there were no sonos devices
2023-01-04 11:16:57,343 discovery.py:747 - _sonos_scan_worker_thread() - Scanning port 192.168.2.198:1400
2023-01-04 11:16:57,353 discovery.py:378 - scan_network() - All 256 scanner threads terminated
2023-01-04 11:16:57,353 zonegroupstate.py:136 - poll() - Cache still active (GetZoneGroupState) during poll for 192.168.2.213
2023-01-04 11:16:57,353 zonegroupstate.py:136 - poll() - Cache still active (GetZoneGroupState) during poll for 192.168.2.194
2023-01-04 11:16:57,353 discovery.py:399 - scan_network() - Include_invisible: True | multi_household: True | 1 Zones: {SoCo("192.168.2.194")}
2023-01-04 11:16:57,354 speakers.py:208 - get_sonos_device_data() - Querying device at 192.168.2.194
2023-01-04 11:16:57,356 connectionpool.py:221 - _new_conn() - Starting new HTTP connection (1): 192.168.2.194:1400
2023-01-04 11:16:57,370 connectionpool.py:428 - _make_request() - http://192.168.2.194:1400 "GET /xml/device_description.xml HTTP/1.1" 200 None
2023-01-04 11:16:57,373 zonegroupstate.py:136 - poll() - Cache still active (GetZoneGroupState) during poll for 192.168.2.194
Sonos Household:
Room/Zone Name IP Address Device Model Visibility SW Version
Right 192.168.2.194 SYMFONISK Visible 9.3
1 Sonos Household(s) found
1 Sonos device(s) found
Saved speaker data at: /home/drtjs/.soco-cli/speakers_v2.pickle
drtjs@myhost:~$
Hello, thank you for sonos-cli I love it !
It would be so nice if you could add some commands like this one :
if_coordinator
: I must do : if [[ $(sonos $speaker if_playing info | grep 'is_coordinator = True') ]] then;
for example for grouping to the coordinator but it take a long time to execute
if_queued
and if_not_queued
: For example for not adding to queue every time we want to play the queue : `sonos $speaker if_queued play_queue : $speaker if_not_queued add_playlist_to queue playlist : play_queue
rel_subgain
: I must do bass_sub_level=$(sonos $speaker sub_gain); new_bass_sub_level=$((bass_sub_level-5)); sonos $speaker sub_gain $new_bass_sub_level"
and it's very long too
rel_treble
: I must do treble_level=$(sonos $speaker treble); new_treble_level=$((treble_level-5)); sonos $speaker treble $new_treble_level
rel_bass
And also some commands for having the state of all speaker :
For example :
info_state
:speaker_1 : is_coordinator = False
Playback is in progress: Artist: xx Album: Untitled (xxx) Title: xx
Volume : 60
Treble : 0
Bass : 0
Balance : 0
Loudness : on
speaker_2 : is_coordinator = True
Using Line In (state: PAUSED_PLAYBACK
Volume : 60
Treble : 0
Bass : -3
Balance : -25
Loudness : off
speaker_3 : Using Line In (state: PAUSED_PLAYBACK)
Volume : 60
Treble : 0
Bass : -3
Balance : 25
Loudness : on
speaker_4 : Using Line In (state: PAUSED_PLAYBACK)
Volume : 25
Treble : -1
Bass : 0
Balance : 0
Loudness : on
speaker_5 : Playback is stopped
Volume : 25
Treble : 0
Bass : 0
Balance : 0
Loudness : on
speaker_6 : Playback is stopped: Channel: Radio.com Elapsed: 0:00:00
Volume : 25
Treble : -3
Bass : 0
Balance : 0
Loudness : on
speaker_Sub : on
Volume : 2
info_playing
:speaker_1 :
Playback is in progress: Artist: xx Album: Untitled Title: xxxx
speaker_2 :
Using Line In (state: PAUSED_PLAYBACK)
speaker_3 :
Using Line In (state: PAUSED_PLAYBACK)
speaker_4 :
Using Line In (state: PAUSED_PLAYBACK)
speaker_5 :
Playback is stopped: Elapsed: 0:00:00
speaker_6 :
Playback is stopped: Channel: Radio.com Elapsed: 0:00:00
sonos speaker_1 speaker_2 speaker_3 group if_coordinator speaker 4
or better the ability to define a coordinator : sonos speaker_1 speaker_2 speaker_3 group $speaker as_coordinator
and this action as_coordinator
make the speaker $speaker the coordinatorI hope you will find the time to do it; it would be so helpful to me.
Thanks :-)
Hello,
I am trying to control Sonos from Raspberry Pi. I am able to control the volume but not other functions. Not sure what I am missing.
I am trying to control status lights, switch the Sonos connect (named Office) to line-in and also select (everywhere) from the group to play music to all speakers.
Below is my script:
import soco
from soco from SoCo
from soco_cli import api
from soco_cli import action_processor as ap
from soco_cli.api import run_command
Can you please help with this?
distutils was removed from python in version 3.12. You might want to add the packaging dependency and use packaging.version.parse in action_processor.py
I can no longer CTRL-C out of a play_file
action on macOS (Ventura 13.6). Tried it on zsh and bash. Definitely used to work!
Seems OK on Linux. Need to test on Windows.
Hi,
my speakers are seperated in an IOT vlan, and I want to use soco-cli.
From network side, everthing is setup correctly and I can reach and find all the speakers although they are in a different network than my smartphone for example.
After playing arround with soco-cli play_file commands, I noticed that it does not work properly because soco-cli is trying to find an ip address in the same address range as the speakers for spinning up the http server.
My question is: is this limitation really necessary?
Thanks in advance
Ubuntu 20.04
python 3.8.10
Just installed soco-0.29.1 and soco-cli-0.4.70 via pip
Sonos Play:1 from a thrift store. Works okay with noson, but I dislike the app. I did some experimenting with noson's cli application and got info on the Play:1's version:
(SONOS)Request: SoftwareVersion = 74.0-43312
(SONOS)Request: DisplaySoftwareVersion = 15.7
(SONOS)Request: HardwareVersion = 1.20.1.6-1.2
(SONOS)Request: IPAddress = 192.168.0.23
(SONOS)Request: MACAddress = 94:9F:3E:16:0A:E0
(SONOS)Request: CopyrightInfo = © 2003-2023, Sonos, Inc. All rights reserved.
I set the SPKR variable, and I get the same results from 'sonos status' 'sonos "Living Roon" status' and 'sonos play_file filename':
Error: Failed to parse: http://192.168.0.23:1400/xml/DeviceProperties1.xml
So it found the Sonos, and appears to have downloaded the properties file, but doesn't like what it sees. I grabbed the file with wget, xmllint doesn't complain about the syntax, kwrite appears to display it correctly, and xmltodict.parse() makes a dict without complaint.
I did a search for the "Failed to parse" message in the soco and soco-cli source, but couldn't find it. So I can't tell who's unhappy, or why. I'm guessing that the version of firmware in my Sonos is producing an XML file with different tag names, or in a different order, or somesuch, but that's just a guess. I've attached a copy so someone can make a more informed analysis.
Based on the API documentation, it is not possible to tell the HTTP API to use the list of cached objects or use a specific subnet. Can you clarify this is the case/consider adding this feature?
First, thanks for this wonderful tool. I've added several alias in my .zshrc to quickly control my Sonos One.
I've added a podcast (French podcast: Le moment Meurice) from the Pocket Casts app in my fav list:
❯ sonos Salon list_favs
1: Flow, votre mix personnalisé (Deezer)
2: France Inter 95.9 (Émissions-débats France)
3: K6 FM 101.6 (Musique Locale)
4: Le moment Meurice
I get this error when I try to play the podcast:
❯ sonos Salon play_fav 'Le moment Meurice'
Error: 1: list index out of range | 2: list index out of range
❯ sonos Salon play_fav "Meurice"
Error: 1: list index out of range | 2: list index out of range
Maybe there is another way to play podcasts from solo-cli ?
Edit: try adding the podcast from TuneIn, same error.
Hi there,
I just discovered this fantastic tool.
The following commands are working:
sonos Chambre sharelink "https://open.spotify.com/track/6cpcorzV5cmVjBsuAXq4wD" start
sonos Chambre play_from_queue 1
Then I put this commands into a macro. It's working with
play_chambre = Chambre sharelink "https://open.spotify.com/track/6cpcorzV5cmVjBsuAXq4wD" start : Chambre play_from_queue 1
Then I tried to add an argument into the URL provided using:
play_chambre = Chambre sharelink %1 start : Chambre play_from_queue 1
When I tried: http://192.168.1.83:8000/macro/play_chambre/https://open.spotify.com/intl-fr/album/4V9BgSnwEMP5yRnZE5flSP
It's not working. I also tried to URL encode the spotify link. It's not working either.
Do you have any idea?
I really want the ability to queue files because it gives better control over play, and was puzzled why it wasn't included.
So I did some experimenting with setting up a simple webserver, and queueing up local URIs. It seems to work, after a fashion, but I've seen some events that suggest that there may be good reasons that the feature is not already done.
I'm okay with only being able to queue files that live within the artist/album/tracks hierarchy that I've been using for decades. So I'm going to firm up the webserver and write some code to generate the URIs. Would you be interested in including that as part of the soco-cli package? If so, I'll build in a config file so people can easily set it up for their own system. I'd also look into writing the URI generation code in a way that it could be incorporated as a sonos action.
I have my fileserver and filename-to-URI translator working. Adding URIs to the queue works, except that, when loading to an empty queue, the first URI winds up at the end, with the others being loaded in the correct sequence ahead of it. Should I always force the fueue position instead of "next"? That worked to get them in order, and I can do a little scripting to retrieve the queue and figure out the right numbers. Meanwhile, at every invocation of sonos add_uri_to_queue, I get one of these error messages. If you let me know where you think they might originate, I can add some debugging code to try to find the cause.
ran@delve:trackserver$ for i in cat cs_uris
;do sonos add_uri_to_queue $i next;done
<function zero_parameters..wrapper at 0x7fe2a85809d0>
<function zero_parameters..wrapper at 0x7f787d97b9d0>
<function zero_parameters..wrapper at 0x7f7966cd9a60>
<function zero_parameters..wrapper at 0x7ff2d9330a60>
<function zero_parameters..wrapper at 0x7f8b053c99d0>
<function zero_parameters..wrapper at 0x7f09344039d0>
<function zero_parameters..wrapper at 0x7f45a108e9d0>
<function zero_parameters..wrapper at 0x7f115cb779d0>
<function zero_parameters..wrapper at 0x7fb2ec2f09d0>
<function zero_parameters..wrapper at 0x7faf7d4269d0>
<function zero_parameters..wrapper at 0x7f5164c47a60>
<function zero_parameters..wrapper at 0x7f7e9368aa60>
Here are the URIs. I don't see anything odd about them. Maybe the numeric IP address instead of a host name??
cs_uris.txt
Was just testing the new version 0.3.8, installed through pip.
For some reason, I don't get an error, however the command line history save is not working.
I'm using Python 3.9.1 on a MacBook Pro running Big Sur.
While browsing through the commit/code I couldn't really figure out why.
After some more testing, this is what finally got it to work as expected:
In a Terminal, type:
cd ~
mkdir .soco-cli
Then from that moment on, sonos will create and append/save to the file shell-history.txt.
Maybe a Python command needs to be added to create this directory in case it doesn't exist?
Keep up the good work :)
Hi,
I tried
soco -l Salon create_alarm '10:00,01:30,WEEKDAYS,ON,"http://stream.live.vc.bbcmedia.co.uk/bbc_radio_fourfm",NORMAL,50,OFF'
soco -l Salon modify_alarm 4 '09:45,01:05,WEEKDAYS,YES,"https://icecast.radiofrance.fr/fipjazz-midfi.mp3",SHUFFLE,15,NO'
and the alarm play the CHIME
Playing radio stream is fine
soco -l Salon play_uri "https://icecast.radiofrance.fr/fipjazz-midfi.mp3"
soco -l Salon play_uri "http://stream.live.vc.bbcmedia.co.uk/bbc_radio_fourfm"
It worked in the past.
Hello Team!
I am getting the following error while trying to utilize sonos-discover to use soco-cli.
Error: 'NoneType' object is not iterable
(soco-cli) ~/projects/temp/soco-cli$ sonos-discover -t 256 -n 1.0 -m 16
Error: 'NoneType' object is not iterable
(soco-cli) ~/projects/temp/soco-cli$ sonos-discover
Error: 'NoneType' object is not iterable
On the first command I was using -m 16 due to my sonos devices being on a different vlan than my computer. I was not able to find a way to manually create a cached set of devices to use in soco-cli.
I setup my env the following way
pipenv install --python 3
pipevn shell
pipenv install soco-cli
Python = Python 3.8.6
OS = POP_OS 20.10
What else can I provide or dig into looking at this?
](url)
The item being played is the entire songs library under My Music (Apple Music)
Other favorites work fine.
Hello, thank you very much for soco-cli, it's really great :-)
I have found a little problem with sonos if_playing
command with grouped devices.
When I group sonos's devices together and when I execute this command :
sonos connect group one : roam group one : roam_2 group one : roam_3 group one
sonos one if_playing volume 20 : connect if_playing rel_vol -6 : roam if_playing volume 13 : roam_2 vol 10 : roam_3 rel_vol 6
Only the volume of sonos one, roam_2 and roam_3 change
So it's only changing the volume of the coordinator one
with ìf_playing
Hi,
I'm a but stumped...
Not sure if I'm using it wrong or if I found an error...
soco lp (list playlists):
1: 1 - Pop l
--- snipp ---
19: WW II - The Pacific
20: xxx
So there is a playlist named xxx:
soco delete_playlist xxx
Error: Playlist 'xxx' not found.
This should work, right?
Debug output is attached...
error.txt
Verified on two macOS Monterey systems, one Intel, one M1.
First of all, thanks for such a great utility! This could be on my end, but trying to confirm how to validate.
I'm using cached discovery of my speakers (6 in total) and am trying to implement some light automation to set the group, and normalize the volume within the group.
I'm using Python 3.10.2
via a clean venv:
# pip list
Package Version
------------------ ---------
anyio 3.5.0
appdirs 1.4.4
asgiref 3.5.0
certifi 2021.10.8
charset-normalizer 2.0.11
click 8.0.3
fastapi 0.73.0
h11 0.13.0
idna 3.3
ifaddr 0.1.7
pip 22.0.3
pydantic 1.9.0
rangehttpserver 1.2.0
requests 2.27.1
setuptools 60.7.1
sniffio 1.2.0
soco 0.26.0
soco-cli 0.4.36
starlette 0.17.1
tabulate 0.8.9
typing_extensions 4.0.1
urllib3 1.26.8
uvicorn 0.17.3
xmltodict 0.12.0
The problem I'm having is that if I leverage:
# sonos -l 10.x.x.65 group-vol 25
...I'd expect the group bound to all be set to a volume level of 25.
I'm starting out with this:
# sonos -l _all_ sysinfo
Office-3:
Report generated on: 2022-02-03 21:52 UTC (Thursday)
Zone Name IP Address Visible CoOrd CoOrd IP Vol. Mute State Model Name Model No. HW Version SW Version
----------- ------------ --------- ------- ----------- ------ ------ ------- ------------ ----------- ------------ -----------------
Dining-5 10.x.x.64 Yes No 10.x.x.65 7 Off STOPPED Play:5 S6 1.13.1.7-1.2 67.1-25031 (14.0)
Kitchen-N-1 10.x.x.63 Yes No 10.x.x.65 4 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Kitchen-S-1 10.x.x.60 Yes No 10.x.x.65 4 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Living-1 10.x.x.61 Yes No 10.x.x.65 4 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Master-1 10.x.x.62 Yes No 10.x.x.65 4 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Office-3 10.x.x.65 Yes Yes 7 Off STOPPED Play:3 S3 1.8.1.3-1.0 67.1-25031 (14.0)
Sonos model numbers present: S1, S3, S6.
Device counts: 6 total Sonos device(s), 3 unique model(s).
But when issue this:
# sonos -l 10.x.x.65 group_vol 50
And then reissue to validate volume levels I end up with something far more random like this:
# sonos -l _all_ sysinfo
Office-3:
Report generated on: 2022-02-03 21:55 UTC (Thursday)
Zone Name IP Address Visible CoOrd CoOrd IP Vol. Mute State Model Name Model No. HW Version SW Version
----------- ------------ --------- ------- ----------- ------ ------ ------- ------------ ----------- ------------ -----------------
Dining-5 10.x.x.64 Yes No 10.x.x.65 56 Off STOPPED Play:5 S6 1.13.1.7-1.2 67.1-25031 (14.0)
Kitchen-N-1 10.x.x.63 Yes No 10.x.x.65 46 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Kitchen-S-1 10.x.x.60 Yes No 10.x.x.65 46 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Living-1 10.x.x.61 Yes No 10.x.x.65 46 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Master-1 10.x.x.62 Yes No 10.x.x.65 46 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Office-3 10.x.x.65 Yes Yes 59 Off STOPPED Play:3 S3 1.8.1.3-1.0 67.1-25031 (14.0)
Sonos model numbers present: S1, S3, S6.
Device counts: 6 total Sonos device(s), 3 unique model(s).
If I go about this individually - the volumes fall in line as expected:
# sonos -l 10.x.x.60 vol 50
# sonos -l 10.x.x.61 vol 50
# sonos -l 10.x.x.62 vol 50
# sonos -l 10.x.x.63 vol 50
# sonos -l 10.x.x.64 vol 50
# sonos -l 10.x.x.65 vol 50
And now look at the system output again, I get what I'm after:
# sonos -l _all_ sysinfo
Office-3:
Report generated on: 2022-02-03 21:58 UTC (Thursday)
Zone Name IP Address Visible CoOrd CoOrd IP Vol. Mute State Model Name Model No. HW Version SW Version
----------- ------------ --------- ------- ----------- ------ ------ ------- ------------ ----------- ------------ -----------------
Dining-5 10.x.x.64 Yes No 10.x.x.65 50 Off STOPPED Play:5 S6 1.13.1.7-1.2 67.1-25031 (14.0)
Kitchen-N-1 10.x.x..63 Yes No 10.x.x.65 50 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Kitchen-S-1 10.x.x.60 Yes No 10.x.x.65 50 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Living-1 10.x.x.61 Yes No 10.x.x.65 50 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Master-1 10.x.x.62 Yes No 10.x.x.65 50 Off STOPPED Play:1 S1 1.8.3.7-1.0 67.1-25031 (14.0)
Office-3 10.x.x.65 Yes Yes 50 Off STOPPED Play:3 S3 1.8.1.3-1.0 67.1-25031 (14.0)
Sonos model numbers present: S1, S3, S6.
Device counts: 6 total Sonos device(s), 3 unique model(s).
Curious what I'm doing incorrectly, I haven't looked at the actual code yet - but wanted to see if this may be expected for some reason before I do.
Thanks, again, for the awesome utility!
Noticed that you support night_mode but not speech enhancement in this awesome tool. Let me know if this something that could get done?
Hi,
A recent update has caused soco-cli to be incompatible with python version versions lower than 3.7. Documenation still says 'Requires Python 3.5+ for most functions. Python 3.7+ is required for the play_file action.', but I get following error on my system that is currently stuck on Python 3.6.8:
root>/bin/python3 --version
Python 3.6.8
root>
root>sonos version
Traceback (most recent call last):
File "/usr/local/bin/sonos", line 5, in
from soco_cli.sonos import main
File "/usr/local/lib/python3.6/site-packages/soco_cli/sonos.py", line 11, in
from .action_processor import list_actions, process_action
File "/usr/local/lib/python3.6/site-packages/soco_cli/action_processor.py", line 19, in
from .play_local_file import is_supported_type, play_local_file
File "/usr/local/lib/python3.6/site-packages/soco_cli/play_local_file.py", line 4, in
from http.server import ThreadingHTTPServer
ImportError: cannot import name 'ThreadingHTTPServer'
Commenting out line 4 in play_local_file.py allows me to run sonos command again, so is it possible to check which version of python is being run, to allow compatibilty for Python lower than 3.7 ?
Thanks :)
AAC and WMA files are supported for playback by Sonos, but don't currently work with the play_file
action. Needs investigation.
https://developer.sonos.com/build/content-service-add-features/supported-audio-formats/
I'm still working on a web frontend and I wonder how I am supposed to reach the prev/next files and on a more general matter how I can retrieve the filenames from the currently playing directory?
I hacked a bit the next
command using seek_forward 1h
because it didn't look to be possible to jump to the next file but it's probably a bad idea(?).
I also retrieve the current playing file extracting it from get_uri
at the moment but I wonder if I'm missing something in the logic and it doesn't allow me to get the directory/album currently playing.
Reading the code, the server doesn't look to keep a reference to the current playing dir.
My hope was digging into the interaction_manager
to learn how to do it but the NEXT command is actually stopping the speaker?
It's starting to take shape!
Hey there,
Thanks to the latest iOS version of the official SONOS app (ahem), I'm trying to make a web interface to soco-cli using the built-in API server and so far so good for my usage:
I'm retrieving the volume from speakers and I'm able to launch a directory full of flac music from my local library (yay!).
But, when I'm hitting async_play_dir
for the second time, the first one is not cancelled even if I paused the speaker.
I guess it's because the play_dir
action is launched as a subprocess and the pid is not kept to be killed once the custom server need to be ended.
@pwt do you have the same interpretation? I know the async feature is quite recent but it actually doesn't work either with the sync call. I guess the json response should return the pid of the process to be killed or the server should keep it in memory to be able to stop it prior to be able to play another directory. I'm willing to initiate a PR with some guidance (and open-source that frontend at some point).
I'm doing home automation with Home Assistant, and as part of this, my speakers get switched off via a smart plug when I leave my home, and get switched on automatically when I return. However, it seems that the queues are saved on the speakers in a non-persistent way since they are gone after switching the speakers back on. I have experimented with SoCo to save the queues before switching speakers off, and restoring them afterwards. This works to some extent, more precisely, it appears to work fine for queues containing songs from my local library (served by a NAS via Samba), but not for, e.g., queues containing a Deezer playlist. My question is whether this might be possible with soco-cli? I must admit that I do not expect this, since it seems to be a problem with SoCo (or even Sonos itself), but I thought that I'd ask anyways :-)
Just in case, here's the code I'm using at the moment:
import json
import time
import traceback
from soco.data_structures import DidlMusicTrack, Queue
from soco.snapshot import Snapshot
from soco.discovery import by_name, discover
state.persist('pyscript.sonos_system_state')
ignored_attributes = {'device', 'media_metadata', 'queue', "__len__"}
@service
def save_sonos_system_state():
start_time = time.time()
devices = [d.group.coordinator for d in task.executor(discover)]
uids = [d.uid for d in devices]
assert len(uids) == len(set(uids))
try:
state.delete("pyscript.sonos_system_state")
except NameError:
pass
pyscript.sonos_system_state = ""
state.setattr("pyscript.sonos_system_state.devices", "")
failed_devices = []
for device in devices:
try:
snapshot = Snapshot(device, snapshot_queue=True)
task.executor(snapshot.snapshot)
snapshot_dict = to_dict(snapshot)
snapshot_json = json.dumps(snapshot_dict)
state.setattr(f"pyscript.sonos_system_state.{device.uid}", snapshot_json)
temp = pyscript.sonos_system_state
temp = device.player_name if temp == "" else f"{temp}, {device.player_name}"
pyscript.sonos_system_state = temp
temp = state.getattr("pyscript.sonos_system_state")["devices"]
temp = device.uid if temp == "" else f"{temp},{device.uid}"
state.setattr(f"pyscript.sonos_system_state.devices", temp)
log.debug(f"Device {device.player_name} (UID: {device.uid}) saved successfully")
except Exception:
failed_devices.append(device)
log.error(f"Error while saving device {device.player_name} (UID: {device.uid}):\n" + repr(traceback.format_exc()))
duration = int(time.time() - start_time)
success_devices = [d for d in devices if d not in failed_devices]
log.info(
f"Saving Sonos system state done: Saved {len(success_devices)} devices ({pyscript.sonos_system_state}), took {duration}s")
@service
def restore_sonos_system_state():
try:
state.get("pyscript.sonos_system_state")
except NameError:
log.warning("Sonos restore called, but no saved system state found. Nothing to do...")
return
start_time = time.time()
state_attributes = state.getattr("pyscript.sonos_system_state")
device_uids = state_attributes["devices"].split(",")
device_names = {}
failed_uids = []
for device_uid in device_uids:
try:
snapshot_json = state_attributes[device_uid]
snapshot_dict = json.loads(snapshot_json)
snapshot = from_dict(snapshot_dict)
device_names[device_uid] = snapshot.device.player_name
task.executor(snapshot.restore, True)
log.debug(f"Device {snapshot.device.player_name} (UID: {device_uid}) restored successfully")
except Exception:
log.error(f"Error while restoring device {device_uid}:\n" + repr(traceback.format_exc()))
failed_uids.append(device_uid)
if len(failed_uids) > 0:
log.error(f"Errors occured while restoring device states (see above). State is not deleted (in case it's a "
f"temporary problem) - try service call again later...")
else:
state.delete("pyscript.sonos_system_state")
duration = int(time.time() - start_time)
success_uids = [u for u in device_uids if u not in failed_uids]
log.info(f"Sonos system restore done: restored {len(success_uids)} devices ({', '.join(map(lambda u : device_names[u], success_uids))}), took {duration}s")
def to_dict(snapshot):
result = {
'attributes': {k: v for k, v in snapshot.__dict__.items() if k not in ignored_attributes},
'device_uid': snapshot.device.uid,
'device_name': snapshot.device.player_name
}
if snapshot.queue:
result['queue'] = [{
"elements": [e.to_dict() for e in q],
"number_returned": q.number_returned,
"total_matches": q.total_matches,
"update_id": q.update_id
} for q in snapshot.queue]
return result
def from_dict(snapshot_dict):
device = find_device(snapshot_dict["device_uid"], snapshot_dict["device_name"])
snapshot = Snapshot(device)
for k, v in snapshot_dict["attributes"].items():
snapshot.__dict__[k] = v
if 'queue' in snapshot_dict:
snapshot.queue = []
for q in snapshot_dict['queue']:
queue = Queue([], q["number_returned"], q["total_matches"], q["update_id"])
for e in q['elements']:
track = DidlMusicTrack.from_dict(e)
track.uri = track.resources[0].uri # hack to avoid SoCo crashing - with this it works :-)
queue.append(track)
snapshot.queue.append(queue)
return snapshot
def find_device(uid, name):
devices = [d.group.coordinator for d in task.executor(discover) if d.uid == uid]
if len(devices) == 0:
raise Exception(f"Did not find device {name} (UID: {uid})")
if len(devices) > 1:
raise Exception(f"Found more then one device with UID {uid}: {', '.join(d.player_name for d in devices)}")
device = devices[0]
if device.player_name != name:
log.warning(f"UID {device.uid}: Name of device ({device.player_name}) differs from name in snapshot ({name}). "
f"This might be because the device has been renamed. Restoring to this device anyways.")
return device
In case you are wondering: There's of course some Home Assistant specific stuff in there - e.g. the state
stuff etc. The main point is that I'm reading the queues, translate them into a JSON serializable dictionary, and save them into the (persistent) Home Assistant state, and (obviously :-) ) go the other direction for restoring the queues.
See #37.
It takes a lot of effort to integrate Sonos API just to make an alternative for people who live in the command line.
Thanks for making this splendid tool and for all the hard work you put in.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.