1 /*
2 * Copyright (C) 2019 Jean-Luc Barriere
3 *
4 * This file is part of Noson-App
5 *
6 * Noson-App is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Noson is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Foobar. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #if (defined(_WIN32) || defined(_WIN64))
22 #define __WINDOWS__
23 #endif
24
25 #ifdef __WINDOWS__
26 #include <WinSock2.h>
27 #include <Windows.h>
28 #include <time.h>
29 #define usleep(t) Sleep((DWORD)(t)/1000)
30 #define sleep(t) Sleep((DWORD)(t)*1000)
31 #else
32 #include <unistd.h>
33 #include <sys/time.h>
34 #endif
35
36 #include <noson/sonossystem.h>
37 #include <noson/sonosplayer.h>
38 #include <noson/contentdirectory.h>
39 #include <noson/didlparser.h>
40 #include <noson/imageservice.h>
41 #include <noson/filestreamer.h>
42 #ifdef HAVE_PULSEAUDIO
43 #include <noson/pulsestreamer.h>
44 #endif
45
46 #include <cstdlib>
47 #include <cstdio>
48 #include <iostream>
49 #include <string>
50 #include <algorithm> // std::find
51
52 #include "tokenizer.h"
53 #include "builtin.h"
54
55 #ifdef __WINDOWS__
56 #define LASTERROR WSAGetLastError()
57 #define ERRNO_INTR WSAEINTR
58 #define FLUSHOUT() fflush(stderr);
59 #define PRINT(a) fprintf(stderr, a)
60 #define PRINT1(a,b) fprintf(stderr, a, b)
61 #define PRINT2(a,b,c) fprintf(stderr, a, b, c)
62 #define PRINT3(a,b,c,d) fprintf(stderr, a, b, c, d)
63 #define PRINT4(a,b,c,d,e) fprintf(stderr, a, b, c, d, e)
64 #else
65 #define LASTERROR errno
66 #define ERRNO_INTR EINTR
67 #define FLUSHOUT() fflush(stdout);
68 #define PRINT(a) fprintf(stdout, a)
69 #define PRINT1(a,b) fprintf(stdout, a, b)
70 #define PRINT2(a,b,c) fprintf(stdout, a, b, c)
71 #define PRINT3(a,b,c,d) fprintf(stdout, a, b, c, d)
72 #define PRINT4(a,b,c,d,e) fprintf(stdout, a, b, c, d, e)
73 #endif
74 #define PERROR(a) fprintf(stderr, a)
75 #define PERROR1(a,b) fprintf(stderr, a, b)
76 #define PERROR2(a,b,c) fprintf(stderr, a, b, c)
77 #define PERROR3(a,b,c,d) fprintf(stderr, a, b, c, d)
78
79 static const char * getCmd(char **begin, char **end, const std::string& option);
80 static const char * getCmdOption(char **begin, char **end, const std::string& option);
81 static void readInStream();
82
83 SONOS::System * gSonos = 0;
84 SONOS::PlayerPtr gPlayer;
85 int gDebug = 0;
86
handleEventCB(void * handle)87 void handleEventCB(void* handle)
88 {
89 if (gDebug > 2)
90 {
91 unsigned char mask = gSonos->LastEvents();
92 if ((mask & SONOS::SVCEvent_AlarmClockChanged))
93 fprintf(stderr, "AlarmClockChanged event triggered\n");
94 if ((mask & SONOS::SVCEvent_ZGTopologyChanged))
95 fprintf(stderr, "ZGTopologyChanged event triggered\n");
96 }
97 }
98
99 /*
100 * the main function
101 */
main(int argc,char ** argv)102 int main(int argc, char** argv)
103 {
104 int ret = 0;
105 SONOS::System::Debug(0);
106
107 if (getCmd(argv, argv + argc, "--help") || getCmd(argv, argv + argc, "-h"))
108 {
109 PRINT("\n --deviceurl <URL>\n\n");
110 PRINT(" Bypass the SSDP discovery by connecting to an endpoint. The typical URLs are:\n");
111 PRINT(" http://{IPADDRESS}:1400 or http://{IPADDRESS}:3400\n");
112 PRINT("\n --debug\n\n");
113 PRINT(" Enable the debug output.\n");
114 PRINT("\n --help | -h\n\n");
115 PRINT(" Print the command usage.\n\n");
116 return EXIT_SUCCESS;
117 }
118
119 if (getCmd(argv, argv + argc, "--debug"))
120 gDebug = 4;
121 SONOS::System::Debug(gDebug);
122
123 const char* deviceUrl = getCmdOption(argv, argv + argc, "--deviceurl");
124
125 #ifdef __WINDOWS__
126 //Initialize Winsock
127 WSADATA wsaData;
128 if ((ret = WSAStartup(MAKEWORD(2, 2), &wsaData)))
129 return ret;
130 #endif /* __WINDOWS__ */
131
132 PRINT1("Noson CLI using libnoson %s, Copyright (C) 2018 Jean-Luc Barriere\n", SONOS::libVersionString());
133 gSonos = new SONOS::System(0, handleEventCB);
134 if (!deviceUrl)
135 {
136 PERROR("Searching... ");
137 if (!gSonos->Discover())
138 {
139 PERROR("No SONOS zone found.\n");
140 return EXIT_FAILURE;
141 }
142 else
143 PERROR("Succeeded\n");
144 }
145 else
146 {
147 PERROR1("Connecting to %s... ", deviceUrl);
148 if (!gSonos->Discover(deviceUrl))
149 {
150 PERROR("The SONOS device is unreachable.\n");
151 return EXIT_FAILURE;
152 }
153 else
154 PERROR("Succeeded\n");
155 }
156
157 /*
158 * Register handlers to process remote request
159 */
160 {
161 SONOS::RequestBrokerPtr imageService(new SONOS::ImageService());
162 gSonos->RegisterRequestBroker(imageService);
163 #ifdef HAVE_PULSEAUDIO
164 gSonos->RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::PulseStreamer(imageService.get())));
165 #endif
166 gSonos->RegisterRequestBroker(SONOS::RequestBrokerPtr(new SONOS::FileStreamer()));
167 }
168 /*
169 * Print Players list
170 */
171 SONOS::ZonePlayerList players = gSonos->GetZonePlayerList();
172 for (SONOS::ZonePlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
173 PRINT2("Found player '%s' with UUID '%s'\n", it->first.c_str(), it->second->GetUUID().c_str());
174 /*
175 * Print Zones list and connect to
176 */
177 SONOS::ZoneList zones = gSonos->GetZoneList();
178 for (SONOS::ZoneList::const_iterator it = zones.begin(); it != zones.end(); ++it)
179 PRINT2("Found zone '%s' with coordinator '%s'\n", it->second->GetZoneName().c_str(), it->second->GetCoordinator()->c_str());
180
181 readInStream();
182
183 if (gPlayer)
184 gPlayer.reset();
185 delete gSonos;
186 gSonos = 0;
187
188 #ifdef __WINDOWS__
189 WSACleanup();
190 #endif /* __WINDOWS__ */
191 return ret;
192 }
193
getCmd(char ** begin,char ** end,const std::string & option)194 static const char * getCmd(char **begin, char **end, const std::string& option)
195 {
196 char **itr = std::find(begin, end, option);
197 if (itr != end)
198 {
199 return *itr;
200 }
201 return NULL;
202 }
203
getCmdOption(char ** begin,char ** end,const std::string & option)204 static const char * getCmdOption(char **begin, char **end, const std::string& option)
205 {
206 for (char** it = begin; it != end; ++it)
207 {
208 if (strncmp(*it, option.c_str(), option.length()) == 0 && (*it)[option.length()] == '=')
209 return &((*it)[option.length() + 1]);
210 }
211 return NULL;
212 }
213
upstr(std::string & str)214 std::string& upstr(std::string& str)
215 {
216 std::string::iterator c = str.begin();
217 while (c != str.end())
218 {
219 *c = toupper(*c);
220 ++c;
221 }
222 return str;
223 }
224
parseCommand(const std::string & line)225 static bool parseCommand(const std::string& line)
226 {
227 std::vector<std::string> tokens;
228 tokenize(line, " ", tokens, true);
229 std::vector<std::string>::const_iterator it = tokens.begin();
230 if (it != tokens.end())
231 {
232 std::string token(*it);
233 upstr(token);
234
235 if (token == "EXIT")
236 return false;
237 else if (token == "")
238 {}
239 else if (token == "HELP")
240 {
241 PRINT("EXIT Exit from CLI\n");
242 PRINT("CONNECT {zone name} Connect to a zone for control\n");
243 PRINT("STATUS Show the playing status\n");
244 #ifdef HAVE_PULSEAUDIO
245 PRINT("PLAYPULSE Play stream from Pulse\n");
246 #endif
247 PRINT("PLAYURL {stream URL} Play stream from URL\n");
248 PRINT("PLAYFV {URI} Play the given favorite\n");
249 PRINT("PLAYSQ {URI} Play the given playlist\n");
250 PRINT("PLAYQUEUE Play queue\n");
251 PRINT("PLAYLINEIN Play line-IN\n");
252 PRINT("PLAYDIGITALIN Play digital-IN/TV\n");
253 PRINT("PLAY Press play\n");
254 PRINT("PAUSE Press pause\n");
255 PRINT("STOP Press stop\n");
256 PRINT("PREVIOUS Press skip previous\n");
257 PRINT("NEXT Press skip next\n");
258 PRINT("SEEK 1.. Seek to track number\n");
259 PRINT("VOLUME 0..100 Set volume master\n");
260 PRINT("VOLUME {player} 0..100 Set volume\n");
261 PRINT("SLEEPTIMER 0..65535 Set sleep timer\n");
262 PRINT("SHOWQUEUE Show queue content\n");
263 PRINT("SHOWFV Show favorites\n");
264 PRINT("SHOWSQ Show playlists\n");
265 PRINT("SHOWAC Show alarms clock\n");
266 PRINT("CREATEAC {1} {2} {3} {4} {5} Create alarm clock using arguments:\n");
267 PRINT(" 1:ROOM The room UUID\n");
268 PRINT(" 2:STARTTIME The time using format HH:MM:SS\n");
269 PRINT(" 3:RECURRENCE The comma separated values of day: SUN,MON,..,SAT\n");
270 PRINT(" 4:DURATION The duration using format HH:MM:SS\n");
271 PRINT(" 5:VOLUME 0..100\n");
272 PRINT("ENABLEAC {id} Enable alarm clock\n");
273 PRINT("DISABLEAC {id} Disable alarm clock\n");
274 PRINT("DESTROYAC {id} Destroy alarm clock\n");
275 PRINT("UPDATEAC {id} {2} {3} Update alarm clock using arguments:\n");
276 PRINT(" 2:Type ROOM,STARTTIME,RECURRENCE,DURATION,VOLUME,PROGRAM\n");
277 PRINT(" 3:New value\n");
278 PRINT(" Program 0 for Buzzer or the index of favorite (see SHOWFV)\n");
279 PRINT("REFRESHSHAREINDEX Update the music index on Sonos\n");
280 PRINT("HELP Print this help\n");
281 PRINT("\n");
282 }
283 else if (token == "CONNECT")
284 {
285 if (++it != tokens.end())
286 {
287 std::string param(*it);
288 while(++it != tokens.end())
289 param.append(" ").append(*it);
290 SONOS::ZoneList zones = gSonos->GetZoneList();
291 bool found = false;
292 for (SONOS::ZoneList::const_iterator iz = zones.begin(); iz != zones.end(); ++iz)
293 {
294 if (iz->second->GetZoneName() == param)
295 {
296 found = true;
297 if ((gPlayer = gSonos->GetPlayer(iz->second, 0, 0)))
298 PERROR1("Connected to zone %s\n", gPlayer->GetZone()->GetZoneName().c_str());
299 else
300 PERROR("Failed\n");
301 break;
302 }
303 }
304 if (!found)
305 PERROR("Not found\n");
306 }
307 else
308 PERROR("Error: Missing arguments.\n");
309 }
310 else if (!gSonos->IsConnected() || !gPlayer)
311 {
312 PERROR("Error: Not connected.\n");
313 }
314 else if (token == "STATUS")
315 {
316 while (gPlayer->TransportPropertyEmpty())
317 sleep(1);
318 SONOS::AVTProperty props = gPlayer->GetTransportProperty();
319 PRINT1("TransportStatus = %s\n", props.TransportStatus.c_str());
320 PRINT1("TransportState = %s\n", props.TransportState.c_str());
321 PRINT1("AVTransportURI = [%s]\n", props.AVTransportURI.c_str());
322 PRINT1("AVTransportTitle = [%s]\n", props.AVTransportURIMetaData ? props.AVTransportURIMetaData->GetValue("dc:title").c_str() : "null");
323 PRINT1("CurrentTrack = %d\n", props.CurrentTrack);
324 PRINT1("CurrentTrackDuration = %s\n", props.CurrentTrackDuration.c_str());
325 PRINT1("CurrentTrackURI = [%s]\n", props.CurrentTrackURI.c_str());
326 PRINT1("CurrentTrackTitle = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("dc:title").c_str() : "null");
327 PRINT1("CurrentTrackAlbum = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("upnp:album").c_str() : "null");
328 PRINT1("CurrentTrackArtist = [%s]\n", props.CurrentTrackMetaData ? props.CurrentTrackMetaData->GetValue("dc:creator").c_str() : "null");
329 PRINT1("CurrentCrossfadeMode = %s\n", props.CurrentCrossfadeMode.c_str());
330 PRINT1("CurrentPlayMode = %s\n", props.CurrentPlayMode.c_str());
331 PRINT1("CurrentTransportActions = %s\n", props.CurrentTransportActions.c_str());
332 PRINT1("NumberOfTracks = %d\n", props.NumberOfTracks);
333 PRINT1("AlarmRunning = %s\n", props.r_AlarmRunning.c_str());
334 PRINT1("AlarmIDRunning = %s\n", props.r_AlarmIDRunning.c_str());
335 PRINT1("AlarmLoggedStartTime = %s\n", props.r_AlarmLoggedStartTime.c_str());
336 PRINT1("AlarmState = %s\n", props.r_AlarmState.c_str());
337
338 SONOS::ElementList vars;
339 if (gPlayer->GetRemainingSleepTimerDuration(vars))
340 {
341 PRINT1("RemainingSleepTimerDuration = %s\n", vars.GetValue("RemainingSleepTimerDuration").c_str());
342 }
343 }
344 else if (token == "SLEEPTIMER")
345 {
346 if (++it != tokens.end())
347 {
348 std::string param(*it);
349 uint16_t value = 0;
350 string_to_uint16(param.c_str(), &value);
351 if (gPlayer->ConfigureSleepTimer((unsigned)value))
352 PERROR("Succeeded\n");
353 else
354 PERROR("Failed\n");
355 }
356 else
357 PERROR("Error: Missing arguments.\n");
358 }
359 else if (token == "SHOWAC")
360 {
361 SONOS::AlarmList alarms = gSonos->GetAlarmList();
362 for (SONOS::AlarmList::const_iterator il = alarms.begin(); il != alarms.end(); ++il)
363 {
364 PRINT("\n");
365 PRINT2("%s: Enabled = %s\n", (*il)->GetId().c_str(), (*il)->GetEnabled() ? "true" : "false");
366 PRINT2("%s: StartTime = %s\n", (*il)->GetId().c_str(), (*il)->GetStartLocalTime().c_str());
367 PRINT2("%s: Recurrence = %s\n", (*il)->GetId().c_str(), (*il)->GetRecurrence().c_str());
368 PRINT2("%s: RoomUUID = %s\n", (*il)->GetId().c_str(), (*il)->GetRoomUUID().c_str());
369 PRINT2("%s: IncludeLinkedZones = %s\n", (*il)->GetId().c_str(), (*il)->GetIncludeLinkedZones() ? "true" : "false");
370 PRINT2("%s: ProgramURI = %s\n", (*il)->GetId().c_str(), (*il)->GetProgramURI().c_str());
371 const SONOS::DigitalItemPtr didl = (*il)->GetProgramMetadata();
372 PRINT2("%s: ProgramTitle = %s\n", (*il)->GetId().c_str(), didl ? didl->GetValue("dc:title").c_str() : "");
373 PRINT2("%s: PlayMode = %s\n", (*il)->GetId().c_str(), (*il)->GetPlayMode().c_str());
374 PRINT2("%s: Volume = %d\n", (*il)->GetId().c_str(), (*il)->GetVolume());
375 PRINT2("%s: Duration = %s\n", (*il)->GetId().c_str(), (*il)->GetDuration().c_str());
376 }
377 }
378 else if (token == "CREATEAC")
379 {
380 std::string roomUUID;
381 std::string start;
382 std::string recurrence("MON,TUE,WED,THU,FRI");
383 std::string duration("01:00:00");
384 uint8_t volume = 20;
385 if (++it != tokens.end())
386 roomUUID.assign(*it);
387 if (++it != tokens.end())
388 start.assign(*it);
389 if (it != tokens.end() && ++it != tokens.end())
390 recurrence.assign(*it);
391 if (it != tokens.end() && ++it != tokens.end())
392 duration.assign(*it);
393 if (it != tokens.end() && ++it != tokens.end())
394 string_to_uint8(it->c_str(), &volume);
395 if (it != tokens.end() && ++it == tokens.end())
396 {
397 SONOS::Alarm alarm;
398 alarm.SetRoomUUID(roomUUID);
399 alarm.SetStartLocalTime(start);
400 alarm.SetRecurrence(recurrence);
401 alarm.SetDuration(duration);
402 alarm.SetVolume(volume);
403 if (gSonos->CreateAlarm(alarm))
404 PERROR("Succeeded\n");
405 else
406 PERROR("Failed\n");
407 }
408 else
409 PERROR("Error: Missing arguments.\n");
410 }
411 else if (token == "ENABLEAC")
412 {
413 if (++it != tokens.end())
414 {
415 SONOS::AlarmList alarms = gSonos->GetAlarmList();
416 SONOS::AlarmPtr ptr;
417 for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
418 {
419 if ((*il)->GetId() == *it)
420 {
421 ptr = *il;
422 break;
423 }
424 }
425 if (ptr)
426 {
427 ptr->SetEnabled(true);
428 if (gSonos->UpdateAlarm(*ptr))
429 PERROR("Succeeded\n");
430 else
431 PERROR("Failed\n");
432 }
433 else
434 PERROR("Error: Invalid alarm ID.\n");
435 }
436 else
437 PERROR("Error: Missing arguments.\n");
438 }
439 else if (token == "DISABLEAC")
440 {
441 if (++it != tokens.end())
442 {
443 SONOS::AlarmList alarms = gSonos->GetAlarmList();
444 SONOS::AlarmPtr ptr;
445 for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
446 {
447 if ((*il)->GetId() == *it)
448 {
449 ptr = *il;
450 break;
451 }
452 }
453 if (ptr)
454 {
455 ptr->SetEnabled(false);
456 if (gSonos->UpdateAlarm(*ptr))
457 PERROR("Succeeded\n");
458 else
459 PERROR("Failed\n");
460 }
461 else
462 PERROR("Error: Invalid alarm ID.\n");
463 }
464 else
465 PERROR("Error: Missing arguments.\n");
466 }
467 else if (token == "DESTROYAC")
468 {
469 if (++it != tokens.end())
470 {
471 if (gSonos->DestroyAlarm(*it))
472 PERROR("Succeeded\n");
473 else
474 PERROR("Failed\n");
475 }
476 else
477 PERROR("Error: Missing arguments.\n");
478 }
479 else if (token == "UPDATEAC")
480 {
481 std::string alarmId;
482 std::string type;
483 if (++it != tokens.end())
484 alarmId.assign(*it);
485 if (it != tokens.end() && ++it != tokens.end())
486 {
487 type.assign(*it);
488 upstr(type);
489 }
490 if (it != tokens.end() && ++it != tokens.end())
491 {
492 SONOS::AlarmList alarms = gSonos->GetAlarmList();
493 SONOS::AlarmPtr ptr;
494 for (SONOS::AlarmList::iterator il = alarms.begin(); il != alarms.end(); ++il)
495 {
496 if ((*il)->GetId() == alarmId)
497 {
498 ptr = *il;
499 break;
500 }
501 }
502 if (ptr)
503 {
504 //ROOM,TIME,RECURRENCE,DURATION,VOLUME
505 if (type == "ROOM")
506 ptr->SetRoomUUID(*it);
507 else if (type == "STARTTIME")
508 ptr->SetStartLocalTime(*it);
509 else if (type == "RECURRENCE")
510 ptr->SetRecurrence(*it);
511 else if (type == "DURATION")
512 ptr->SetDuration(*it);
513 else if (type == "VOLUME")
514 {
515 uint8_t value = 0;
516 string_to_uint8(it->c_str(), &value);
517 ptr->SetVolume(value);
518 }
519 else if (type == "PROGRAM")
520 {
521 if (*it == "0")
522 {
523 ptr->SetProgramURI(ALARM_BUZZER_URI);
524 ptr->SetProgramMetadata(SONOS::DigitalItemPtr());
525 }
526 else
527 {
528 uint16_t value = 0;
529 string_to_uint16(it->c_str(), &value);
530 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
531 SONOS::ContentList bdir(mycontent, "FV:2");
532 SONOS::ContentList::iterator ic = bdir.begin();
533 uint16_t i = 0;
534 while (ic != bdir.end())
535 {
536 if (++i == value)
537 {
538 SONOS::DigitalItemPtr metaPtr;
539 if (SONOS::System::ExtractObjectFromFavorite(*ic, metaPtr))
540 {
541 ptr->SetProgramURI(metaPtr->GetValue("res"));
542 ptr->SetProgramMetadata(metaPtr);
543 }
544 break;
545 }
546 ++ic;
547 }
548 }
549 }
550 if (gSonos->UpdateAlarm(*ptr))
551 PERROR("Succeeded\n");
552 else
553 PERROR("Failed\n");
554 }
555 else
556 PERROR("Error: Invalid alarm ID.\n");
557 }
558 else
559 PERROR("Error: Missing arguments.\n");
560 }
561 else if (token == "PLAY")
562 {
563 if (gPlayer->Play())
564 PERROR("Succeeded\n");
565 else
566 PERROR("Failed\n");
567 }
568 else if (token == "STOP")
569 {
570 if (gPlayer->Stop())
571 PERROR("Succeeded\n");
572 else
573 PERROR("Failed\n");
574 }
575 else if (token == "PAUSE")
576 {
577 if (gPlayer->Pause())
578 PERROR("Succeeded\n");
579 else
580 PERROR("Failed\n");
581 }
582 else if (token == "PREVIOUS")
583 {
584 if (gPlayer->Previous())
585 PERROR("Succeeded\n");
586 else
587 PERROR("Failed\n");
588 }
589 else if (token == "NEXT")
590 {
591 if (gPlayer->Next())
592 PERROR("Succeeded\n");
593 else
594 PERROR("Failed\n");
595 }
596 else if (token == "SEEK")
597 {
598 if (++it != tokens.end())
599 {
600 uint32_t value = 0;
601 string_to_uint32(it->c_str(), &value);
602 if (gPlayer->SeekTrack(value))
603 PERROR("Succeeded\n");
604 else
605 PERROR("Failed\n");
606 }
607 else
608 PERROR("Error: Missing arguments.\n");
609 }
610 #ifdef HAVE_PULSEAUDIO
611 else if (token == "PLAYPULSE")
612 {
613 if (gPlayer->PlayPulse())
614 PERROR("Succeeded\n");
615 else
616 PERROR("Failed\n");
617 }
618 #endif
619 else if (token == "PLAYURL")
620 {
621 if (++it != tokens.end())
622 {
623 std::string param(*it);
624 while(++it != tokens.end())
625 param.append(" ").append(*it);
626 if (gPlayer->PlayStream(param, ""))
627 PERROR("Succeeded\n");
628 else
629 PERROR("Failed\n");
630 }
631 else
632 PERROR("Error: Missing arguments.\n");
633 }
634 else if (token == "VOLUME")
635 {
636 if (++it != tokens.end())
637 {
638 bool all = true;
639 std::string param(*it);
640 std::string param2;
641 while(++it != tokens.end())
642 {
643 all = false;
644 if ((it + 1) == tokens.end())
645 param2.append(*it);
646 else
647 param.append(" ").append(*it);
648 }
649 if (all)
650 param2.assign(param);
651 SONOS::ZonePtr pl = gPlayer->GetZone();
652 for (SONOS::Zone::iterator ip = pl->begin(); ip != pl->end(); ++ip)
653 {
654 if (all || param == **ip)
655 {
656 uint8_t value = 0;
657 string_to_uint8(param2.c_str(), &value);
658 if (gPlayer->SetVolume((*ip)->GetUUID(), value))
659 PERROR3("%s [%s]: volume %u\n", (*ip)->c_str(), (*ip)->GetUUID().c_str(), value);
660 else
661 PERROR2("%s [%s]: Failed\n", (*ip)->c_str(), (*ip)->GetUUID().c_str());
662 }
663 }
664 }
665 else
666 PERROR("Error: Missing arguments.\n");
667 }
668 else if (token == "SHOWQUEUE")
669 {
670 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
671 SONOS::ContentList bdir(mycontent, "Q:0");
672 PRINT1("UpdateID : %u\n", bdir.GetUpdateID());
673 PRINT1("Item count: %u\n", bdir.size());
674 SONOS::ContentList::iterator ic = bdir.begin();
675 int i = 0;
676 while (ic != bdir.end())
677 {
678 PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
679 ++ic;
680 }
681 }
682 else if (token == "PLAYQUEUE")
683 {
684 if (gPlayer->PlayQueue(true))
685 PERROR("Succeeded\n");
686 else
687 PERROR("Failed\n");
688 }
689 else if (token == "PLAYLINEIN")
690 {
691 if (gPlayer->PlayLineIN())
692 PERROR("Succeeded\n");
693 else
694 PERROR("Failed\n");
695 }
696 else if (token == "PLAYDIGITALIN")
697 {
698 if (gPlayer->PlayDigitalIN())
699 PERROR("Succeeded\n");
700 else
701 PERROR("Failed\n");
702 }
703 else if (token == "SHOWFV")
704 {
705 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
706 SONOS::ContentList bdir(mycontent, "FV:2");
707 PRINT1("UpdateID : %u\n", bdir.GetUpdateID());
708 PRINT1("Item count: %u\n", bdir.size());
709 SONOS::ContentList::iterator ic = bdir.begin();
710 int i = 0;
711 while (ic != bdir.end())
712 {
713 PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
714 ++ic;
715 }
716 }
717 else if (token == "PLAYFV")
718 {
719 if (++it != tokens.end())
720 {
721 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
722 SONOS::ContentList bdir(mycontent, "FV:2");
723 SONOS::ContentList::iterator ic = bdir.begin();
724 while (ic != bdir.end())
725 {
726 if ((*ic)->GetValue("res") == (*it))
727 {
728 SONOS::DigitalItemPtr item;
729 if (SONOS::System::ExtractObjectFromFavorite((*ic), item))
730 {
731 if (SONOS::System::CanQueueItem(item))
732 {
733 PRINT2("Playing item [%s] [%s]\n", (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
734 if (gPlayer->RemoveAllTracksFromQueue() && gPlayer->PlayQueue(false) && gPlayer->AddURIToQueue(item, 1) && gPlayer->SeekTrack(1) && gPlayer->Play())
735 PERROR("Succeeded\n");
736 else
737 PERROR("Failed\n");
738 }
739 else if (gPlayer->SetCurrentURI(item) && gPlayer->Play())
740 PERROR("Succeeded\n");
741 else
742 PERROR("Failed\n");
743 }
744 else
745 PERROR("Failed\n");
746 }
747 ++ic;
748 }
749 }
750 else
751 PERROR("Error: Missing arguments.\n");
752 }
753 else if (token == "SHOWSQ")
754 {
755 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
756 SONOS::ContentList bdir(mycontent, "SQ:");
757 PRINT1("UpdateID : %u\n", bdir.GetUpdateID());
758 PRINT1("Item count: %u\n", bdir.size());
759 SONOS::ContentList::iterator ic = bdir.begin();
760 int i = 0;
761 while (ic != bdir.end())
762 {
763 PRINT3("%d: [%s] [%s]\n", ++i, (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
764 ++ic;
765 }
766 }
767 else if (token == "PLAYSQ")
768 {
769 if (++it != tokens.end())
770 {
771 SONOS::ContentDirectory mycontent(gSonos->GetHost(), gSonos->GetPort());
772 SONOS::ContentList bdir(mycontent, "SQ:");
773 SONOS::ContentList::iterator ic = bdir.begin();
774 while (ic != bdir.end())
775 {
776 if ((*ic)->GetValue("res") == (*it))
777 {
778 PRINT2("Playing item [%s] [%s]\n", (*ic)->GetValue("dc:title").c_str(), (*ic)->GetValue("res").c_str());
779 if (gPlayer->RemoveAllTracksFromQueue() && gPlayer->PlayQueue(false) && gPlayer->AddURIToQueue(*ic, 1) && gPlayer->SeekTrack(1) && gPlayer->Play())
780 PERROR("Succeeded\n");
781 else
782 PERROR("Failed\n");
783 }
784 ++ic;
785 }
786 }
787 else
788 PERROR("Error: Missing arguments.\n");
789 }
790 else if (token == "REFRESHSHAREINDEX")
791 {
792 if (gSonos->RefreshShareIndex())
793 PERROR("Succeeded\n");
794 else
795 PERROR("Failed\n");
796 }
797 else
798 {
799 PERROR("Error: Command invalid.\n");
800 }
801 }
802 return true;
803 }
804
prompt()805 static void prompt() {
806 if (gSonos->IsConnected() && gPlayer)
807 PRINT1("%s >>> ", gPlayer->GetZone()->GetZoneName().c_str());
808 else
809 PRINT(">>> ");
810 FLUSHOUT();
811 }
812
readInStream()813 static void readInStream()
814 {
815 static int maxlen = 1023;
816 char* buf = new char[maxlen + 1];
817 size_t len = 0;
818 bool run = true;
819 #ifndef __WINDOWS__
820 fd_set fds;
821 #endif
822
823 prompt();
824
825 while (run)
826 {
827 #ifndef __WINDOWS__
828 struct timeval tv;
829 tv.tv_sec = 1;
830 tv.tv_usec = 0;
831 FD_ZERO(&fds);
832 FD_SET(STDIN_FILENO, &fds);
833 int r = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
834 if (r > 0 && FD_ISSET(STDIN_FILENO, &fds))
835 #endif
836 {
837 int chr;
838 while (run && (chr = getchar()) != EOF)
839 {
840 if (chr != '\n')
841 {
842 if (len < maxlen)
843 buf[len++] = (char) chr;
844 }
845 else
846 {
847 buf[len] = '\0';
848 if ((run = parseCommand(buf)))
849 {
850 len = 0;
851 prompt();
852 }
853 }
854 }
855 }
856 #ifndef __WINDOWS__
857 else if (r < 0)
858 {
859 if (LASTERROR == ERRNO_INTR)
860 continue;
861 else
862 break;
863 }
864 #endif
865 }
866
867 delete[] buf;
868 }
869