1 #include "stdafx.h"
2 #include "LogitechMediaServer.h"
3 #include "../hardware/hardwaretypes.h"
4 #include "../main/json_helper.h"
5 #include "../main/Helper.h"
6 #include "../main/localtime_r.h"
7 #include "../main/Logger.h"
8 #include "../main/mainworker.h"
9 #include "../main/SQLHelper.h"
10 #include "../main/WebServer.h"
11 #include "../notifications/NotificationHelper.h"
12 #include "../httpclient/HTTPClient.h"
13
CLogitechMediaServer(const int ID,const std::string & IPAddress,const int Port,const std::string & User,const std::string & Pwd,const int PollIntervalsec)14 CLogitechMediaServer::CLogitechMediaServer(const int ID, const std::string &IPAddress, const int Port, const std::string &User, const std::string &Pwd, const int PollIntervalsec) :
15 m_IP(IPAddress),
16 m_User(User),
17 m_Pwd(Pwd),
18 m_iThreadsRunning(0)
19 {
20 m_HwdID = ID;
21 m_Port = Port;
22 m_bShowedStartupMessage = false;
23 m_iMissedQueries = 0;
24 SetSettings(PollIntervalsec);
25 }
26
CLogitechMediaServer(const int ID)27 CLogitechMediaServer::CLogitechMediaServer(const int ID) :
28 m_iThreadsRunning(0)
29 {
30 m_HwdID = ID;
31 m_Port = 0;
32 m_iMissedQueries = 0;
33 m_bShowedStartupMessage = false;
34 std::vector<std::vector<std::string> > result;
35 result = m_sql.safe_query("SELECT Address, Port, Username, Password FROM Hardware WHERE ID==%d", m_HwdID);
36
37 if (!result.empty())
38 {
39 m_IP = result[0][0];
40 m_Port = atoi(result[0][1].c_str());
41 m_User = result[0][2];
42 m_Pwd = result[0][3];
43 }
44
45 SetSettings(10);
46 }
47
~CLogitechMediaServer(void)48 CLogitechMediaServer::~CLogitechMediaServer(void)
49 {
50 m_bIsStarted = false;
51 }
52
Query(const std::string & sIP,const int iPort,const std::string & sPostdata)53 Json::Value CLogitechMediaServer::Query(const std::string &sIP, const int iPort, const std::string &sPostdata)
54 {
55 Json::Value root;
56 std::vector<std::string> ExtraHeaders;
57 std::string sResult;
58 std::stringstream sURL;
59 std::stringstream sPostData;
60
61 if ((m_User != "") && (m_Pwd != ""))
62 sURL << "http://" << m_User << ":" << m_Pwd << "@" << sIP << ":" << iPort << "/jsonrpc.js";
63 else
64 sURL << "http://" << sIP << ":" << iPort << "/jsonrpc.js";
65
66 sPostData << sPostdata;
67
68 HTTPClient::SetTimeout(5);
69 bool bRetVal = HTTPClient::POST(sURL.str(), sPostData.str(), ExtraHeaders, sResult);
70
71 if (!bRetVal)
72 {
73 return root;
74 }
75 bRetVal = ParseJSon(sResult, root);
76 if ((!bRetVal) || (!root.isObject()))
77 {
78 size_t aFind = sResult.find("401 Authorization Required");
79 if ((aFind > 0) && (aFind != std::string::npos))
80 _log.Log(LOG_ERROR, "Logitech Media Server: Username and/or password are incorrect. Check Logitech Media Server settings.");
81 else
82 _log.Log(LOG_ERROR, "Logitech Media Server: PARSE ERROR: %s", sResult.c_str());
83 return root;
84 }
85 if (root["method"].empty())
86 {
87 _log.Log(LOG_ERROR, "Logitech Media Server: '%s' request '%s'", sURL.str().c_str(), sPostData.str().c_str());
88 return root;
89 }
90 return root["result"];
91 }
92
NotificationType(_eMediaStatus nStatus)93 _eNotificationTypes CLogitechMediaServer::NotificationType(_eMediaStatus nStatus)
94 {
95 switch (nStatus)
96 {
97 case MSTAT_OFF: return NTYPE_SWITCH_OFF;
98 case MSTAT_ON: return NTYPE_SWITCH_ON;
99 case MSTAT_PAUSED: return NTYPE_PAUSED;
100 case MSTAT_STOPPED: return NTYPE_STOPPED;
101 case MSTAT_PLAYING: return NTYPE_PLAYING;
102 default: return NTYPE_SWITCH_OFF;
103 }
104 }
105
StartHardware()106 bool CLogitechMediaServer::StartHardware()
107 {
108 StopHardware();
109
110 RequestStart();
111
112 m_bIsStarted = true;
113 sOnConnected(this);
114 m_iThreadsRunning = 0;
115 m_bShowedStartupMessage = false;
116
117 StartHeartbeatThread();
118
119 //Start worker thread
120 m_thread = std::make_shared<std::thread>(&CLogitechMediaServer::Do_Work, this);
121 SetThreadNameInt(m_thread->native_handle());
122
123 return (m_thread != nullptr);
124 }
125
StopHardware()126 bool CLogitechMediaServer::StopHardware()
127 {
128 StopHeartbeatThread();
129
130 if (m_thread)
131 {
132 RequestStop();
133 m_thread->join();
134 m_thread.reset();
135 }
136 m_bIsStarted = false;
137 return true;
138 }
139
UpdateNodeStatus(const LogitechMediaServerNode & Node,const _eMediaStatus nStatus,const std::string & sStatus,bool bPingOK)140 void CLogitechMediaServer::UpdateNodeStatus(const LogitechMediaServerNode &Node, const _eMediaStatus nStatus, const std::string &sStatus, bool bPingOK)
141 {
142 //This has to be rebuild! No direct poking in the database, please use CMainWorker::UpdateDevice
143
144 //Find out node, and update it's status
145 std::vector<LogitechMediaServerNode>::iterator itt;
146 for (itt = m_nodes.begin(); itt != m_nodes.end(); ++itt)
147 {
148 if (itt->ID == Node.ID)
149 {
150 //Found it
151 //Retrieve devicename instead of playername in case it was renamed...
152 std::string sDevName = itt->Name;
153 std::vector<std::vector<std::string> > result;
154 result = m_sql.safe_query("SELECT Name FROM DeviceStatus WHERE DeviceID=='%q'", itt->szDevID);
155 if (result.size() == 1) {
156 std::vector<std::string> sd = result[0];
157 sDevName = sd[0];
158 }
159 bool bUseOnOff = false;
160 if (((nStatus == MSTAT_OFF) && bPingOK) || ((nStatus != MSTAT_OFF) && !bPingOK)) bUseOnOff = true;
161 time_t atime = mytime(NULL);
162 itt->LastOK = atime;
163 if ((itt->nStatus != nStatus) || (itt->sStatus != sStatus))
164 {
165 // 1: Update the DeviceStatus
166 if ((nStatus == MSTAT_PLAYING) || (nStatus == MSTAT_PAUSED) || (nStatus == MSTAT_STOPPED))
167 _log.Log(LOG_NORM, "Logitech Media Server: (%s) %s - '%s'", Node.Name.c_str(), Media_Player_States(nStatus), sStatus.c_str());
168 else
169 _log.Log(LOG_NORM, "Logitech Media Server: (%s) %s", Node.Name.c_str(), Media_Player_States(nStatus));
170 struct tm ltime;
171 localtime_r(&atime, <ime);
172 char szLastUpdate[40];
173 sprintf(szLastUpdate, "%04d-%02d-%02d %02d:%02d:%02d", ltime.tm_year + 1900, ltime.tm_mon + 1, ltime.tm_mday, ltime.tm_hour, ltime.tm_min, ltime.tm_sec);
174 result = m_sql.safe_query("UPDATE DeviceStatus SET nValue=%d, sValue='%q', LastUpdate='%q' WHERE (HardwareID == %d) AND (DeviceID == '%q') AND (Unit == 1) AND (SwitchType == %d)",
175 int(nStatus), sStatus.c_str(), szLastUpdate, m_HwdID, itt->szDevID, STYPE_Media);
176
177 // 2: Log the event if the actual status has changed
178 std::string sShortStatus = sStatus;
179 if ((itt->nStatus != nStatus) || (itt->sShortStatus != sShortStatus))
180 {
181 std::string sLongStatus = Media_Player_States(nStatus);
182 if ((nStatus == MSTAT_PLAYING) || (nStatus == MSTAT_PAUSED) || (nStatus == MSTAT_STOPPED))
183 if (sShortStatus.length()) sLongStatus += " - " + sShortStatus;
184 result = m_sql.safe_query("INSERT INTO LightingLog (DeviceRowID, nValue, sValue, User) VALUES (%d, %d, '%q','%q')", itt->ID, int(nStatus), sLongStatus.c_str(), "Logitech");
185 }
186
187 // 3: Trigger On/Off actions
188 if (bUseOnOff)
189 {
190 result = m_sql.safe_query("SELECT StrParam1,StrParam2 FROM DeviceStatus WHERE (HardwareID==%d) AND (ID = '%q') AND (Unit == 1)", m_HwdID, itt->szDevID);
191 if (!result.empty())
192 {
193 m_sql.HandleOnOffAction(bPingOK, result[0][0], result[0][1]);
194 }
195 }
196
197 // 4: Trigger Notifications & events on status change
198 if (itt->nStatus != nStatus)
199 {
200 m_notifications.CheckAndHandleNotification(itt->ID, sDevName, NotificationType(nStatus), sStatus.c_str());
201 m_mainworker.m_eventsystem.ProcessDevice(m_HwdID, itt->ID, 1, int(pTypeLighting2), int(sTypeAC), 12, 100, int(nStatus), sStatus.c_str(), sDevName);
202 }
203
204 itt->nStatus = nStatus;
205 itt->sStatus = sStatus;
206 itt->sShortStatus = sShortStatus;
207 }
208 break;
209 }
210 }
211 }
212
Do_Node_Work(const LogitechMediaServerNode & Node)213 void CLogitechMediaServer::Do_Node_Work(const LogitechMediaServerNode &Node)
214 {
215 bool bPingOK = false;
216 _eMediaStatus nStatus = MSTAT_UNKNOWN;
217 _eMediaStatus nOldStatus = Node.nStatus;
218 std::string sPlayerId = Node.IP;
219 std::string sStatus = "";
220
221 try
222 {
223 std::string sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + sPlayerId + "\",[\"status\",\"-\",1,\"tags:Aadly\"]]}";
224 Json::Value root = Query(m_IP, m_Port, sPostdata);
225
226 if (root.isNull())
227 nStatus = MSTAT_DISCONNECTED;
228 else
229 {
230 bPingOK = true;
231
232 if (root["player_connected"].asString() == "1")
233 {
234 if (root["power"].asString() == "0")
235 nStatus = MSTAT_OFF;
236 else {
237 std::string sMode = root["mode"].asString();
238 if (sMode == "play")
239 if ((nOldStatus == MSTAT_OFF) || (nOldStatus == MSTAT_DISCONNECTED))
240 nStatus = MSTAT_ON;
241 else
242 nStatus = MSTAT_PLAYING;
243 else if (sMode == "pause")
244 if ((nOldStatus == MSTAT_OFF) || (nOldStatus == MSTAT_DISCONNECTED))
245 nStatus = MSTAT_ON;
246 else
247 nStatus = MSTAT_PAUSED;
248 else if (sMode == "stop")
249 if ((nOldStatus == MSTAT_OFF) || (nOldStatus == MSTAT_DISCONNECTED))
250 nStatus = MSTAT_ON;
251 else
252 nStatus = MSTAT_STOPPED;
253 else
254 nStatus = MSTAT_ON;
255 std::string sTitle = "";
256 std::string sAlbum = "";
257 std::string sArtist = "";
258 std::string sAlbumArtist = "";
259 std::string sTrackArtist = "";
260 std::string sYear = "";
261 std::string sDuration = "";
262 std::string sLabel = "";
263
264 if (root["playlist_loop"].size()) {
265 sTitle = root["playlist_loop"][0]["title"].asString();
266 sAlbum = root["playlist_loop"][0]["album"].asString();
267 sArtist = root["playlist_loop"][0]["artist"].asString();
268 sAlbumArtist = root["playlist_loop"][0]["albumartist"].asString();
269 sTrackArtist = root["playlist_loop"][0]["trackartist"].asString();
270 sYear = root["playlist_loop"][0]["year"].asString();
271 sDuration = root["playlist_loop"][0]["duration"].asString();
272
273 if (sTrackArtist != "")
274 sArtist = sTrackArtist;
275 else
276 if (sAlbumArtist != "")
277 sArtist = sAlbumArtist;
278 if (sYear == "0") sYear = "";
279 if (sYear != "")
280 sYear = " (" + sYear + ")";
281
282 sLabel = sArtist + " - " + sTitle + sYear;
283 }
284 else
285 sLabel = "(empty playlist)";
286
287 sStatus = sLabel;
288 }
289 }
290 else
291 nStatus = MSTAT_DISCONNECTED;
292 }
293 }
294 catch (...)
295 {
296 bPingOK = false;
297 }
298 UpdateNodeStatus(Node, nStatus, sStatus, bPingOK);
299 if (m_iThreadsRunning > 0) m_iThreadsRunning--;
300 }
301
Do_Work()302 void CLogitechMediaServer::Do_Work()
303 {
304 int mcounter = 0;
305 int scounter = 0;
306 bool bFirstTime = true;
307
308 _log.Log(LOG_STATUS, "Logitech Media Server: Worker started...");
309
310 ReloadNodes();
311 ReloadPlaylists();
312
313 //Mark devices as 'Unused'
314 m_sql.safe_query("UPDATE WOLNodes SET Timeout=-1 WHERE (HardwareID==%d)", m_HwdID);
315
316 while (!IsStopRequested(500))
317 {
318 mcounter++;
319 if (mcounter == 2)
320 {
321 mcounter = 0;
322 scounter++;
323 if ((scounter >= m_iPollInterval) || (bFirstTime))
324 {
325 std::lock_guard<std::mutex> l(m_mutex);
326
327 scounter = 0;
328 bFirstTime = false;
329
330 GetPlayerInfo();
331
332 std::vector<LogitechMediaServerNode>::const_iterator itt;
333 for (itt = m_nodes.begin(); itt != m_nodes.end(); ++itt)
334 {
335 if (IsStopRequested(0))
336 return;
337 if (m_iThreadsRunning < 1000)
338 {
339 m_iThreadsRunning++;
340 boost::thread t(boost::bind(&CLogitechMediaServer::Do_Node_Work, this, *itt));
341 SetThreadName(t.native_handle(), "LogitechNode");
342 t.join();
343 }
344 }
345 }
346 }
347 }
348 //Make sure all our background workers are stopped
349 while (m_iThreadsRunning > 0)
350 {
351 sleep_milliseconds(150);
352 }
353
354 _log.Log(LOG_STATUS, "Logitech Media Server: Worker stopped...");
355 }
356
GetPlayerInfo()357 void CLogitechMediaServer::GetPlayerInfo()
358 {
359 try
360 {
361 std::string sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"\",[\"serverstatus\",0,999]]}";
362 Json::Value root = Query(m_IP, m_Port, sPostdata);
363
364 if (root.isNull()) {
365 m_iMissedQueries++;
366 if (m_iMissedQueries % 3 == 0) {
367 _log.Log(LOG_ERROR, "Logitech Media Server: No response from server %s:%i", m_IP.c_str(), m_Port);
368 }
369 }
370 else {
371 SetHeartbeatReceived();
372 m_iMissedQueries = 0;
373
374 int totPlayers = root["player count"].asInt();
375 if (totPlayers > 0) {
376 if (!m_bShowedStartupMessage)
377 _log.Log(LOG_STATUS, "Logitech Media Server: %i connected player(s) found.", totPlayers);
378 for (int ii = 0; ii < totPlayers; ii++)
379 {
380 if (root["players_loop"][ii]["name"].empty())
381 continue;
382
383 //int isplayer = root["players_loop"][ii]["isplayer"].asInt();
384 std::string name = root["players_loop"][ii]["name"].asString();
385 std::string model = root["players_loop"][ii]["model"].asString();
386 std::string ipport = root["players_loop"][ii]["ip"].asString();
387 std::string macaddress = root["players_loop"][ii]["playerid"].asString();
388 std::vector<std::string> IPPort;
389 StringSplit(ipport, ":", IPPort);
390 if (IPPort.size() < 2)
391 continue; //invalid ip:port
392 std::string ip = IPPort[0];
393 //int port = atoi(IPPort[1].c_str());
394
395 if (
396 //(model == "slimp3") || //SliMP3
397 (model == "Squeezebox") || //Squeezebox 1
398 (model == "squeezebox2") || //Squeezebox 2
399 (model == "squeezebox3") || //Squeezebox 3
400 (model == "transporter") || //Transporter
401 (model == "receiver") || //Squeezebox Receiver
402 (model == "boom") || //Squeezebox Boom
403 //(model == "softsqueeze") || //Softsqueeze
404 (model == "controller") || //Squeezebox Controller
405 (model == "squeezeplay") || //SqueezePlay
406 (model == "squeezeplayer") || //SqueezePlay
407 (model == "baby") || //Squeezebox Radio
408 (model == "fab4") || //Squeezebox Touch
409 (model == "iPengiPod") || //iPeng iPhone App
410 (model == "iPengiPad") || //iPeng iPad App
411 (model == "squeezelite") || //Max2Play SqueezePlug
412 (model == "daphile") //Audiophile Music Server & Player OS
413 )
414 {
415 UpsertPlayer(name, ip, macaddress);
416 }
417 else {
418 if (!m_bShowedStartupMessage)
419 _log.Log(LOG_ERROR, "Logitech Media Server: model '%s' not supported.", model.c_str());
420 }
421 }
422 }
423 else {
424 if (!m_bShowedStartupMessage)
425 _log.Log(LOG_ERROR, "Logitech Media Server: No connected players found.");
426 }
427 m_bShowedStartupMessage = true;
428 }
429 }
430 catch (...)
431 {
432 }
433 }
434
UpsertPlayer(const std::string & Name,const std::string & IPAddress,const std::string & MacAddress)435 void CLogitechMediaServer::UpsertPlayer(const std::string &Name, const std::string &IPAddress, const std::string &MacAddress)
436 {
437 std::vector<std::vector<std::string> > result;
438
439 //Check if exists...
440 //1) Check on MacAddress (default)
441 result = m_sql.safe_query("SELECT Name FROM WOLNodes WHERE (HardwareID==%d) AND (MacAddress=='%q')", m_HwdID, MacAddress.c_str());
442 if (!result.empty()) {
443 if (result[0][0].c_str() != Name) { //Update Name in case it has been changed
444 m_sql.safe_query("UPDATE WOLNodes SET Name='%q', Timeout=0 WHERE (HardwareID==%d) AND (MacAddress=='%q')", Name.c_str(), m_HwdID, MacAddress.c_str());
445 _log.Log(LOG_STATUS, "Logitech Media Server: Player '%s' renamed to '%s'", result[0][0].c_str(), Name.c_str());
446 }
447 else { //Mark device as 'Active'
448 m_sql.safe_query("UPDATE WOLNodes SET Timeout=0 WHERE (HardwareID==%d) AND (MacAddress=='%q')", m_HwdID, MacAddress.c_str());
449 }
450 return;
451 }
452
453 //2) Check on Name+IP (old format); Convert IP -> MacAddress
454 result = m_sql.safe_query("SELECT ID FROM WOLNodes WHERE (HardwareID==%d) AND (Name=='%q') AND (MacAddress=='%q')", m_HwdID, Name.c_str(), IPAddress.c_str());
455 if (!result.empty()) {
456 m_sql.safe_query("UPDATE WOLNodes SET MacAddress='%q', Timeout=0 WHERE (HardwareID==%d) AND (Name=='%q') AND (MacAddress=='%q')", MacAddress.c_str(), m_HwdID, Name.c_str(), IPAddress.c_str());
457 _log.Log(LOG_STATUS, "Logitech Media Server: Player '%s' IP changed to MacAddress", Name.c_str());
458 return;
459 }
460
461 //3) Check on Name (old format), IP changed?; Convert IP -> MacAddress
462 result = m_sql.safe_query("SELECT ID FROM WOLNodes WHERE (HardwareID==%d) AND (Name=='%q')", m_HwdID, Name.c_str());
463 if (!result.empty()) {
464 m_sql.safe_query("UPDATE WOLNodes SET MacAddress='%q', Timeout=0 WHERE (HardwareID==%d) AND (Name=='%q')", MacAddress.c_str(), m_HwdID, Name.c_str());
465 _log.Log(LOG_STATUS, "Logitech Media Server: Player '%s' IP changed to MacAddress", Name.c_str());
466 return;
467 }
468
469 //4) Check on IP (old format), Name changed?; Convert IP -> MacAddress
470 result = m_sql.safe_query("SELECT ID FROM WOLNodes WHERE (HardwareID==%d) AND (MacAddress=='%q')", m_HwdID, IPAddress.c_str());
471 if (!result.empty()) {
472 m_sql.safe_query("UPDATE WOLNodes SET Name='%q', MacAddress='%q', Timeout=0 WHERE (HardwareID==%d) AND (MacAddress=='%q')", Name.c_str(), MacAddress.c_str(), m_HwdID, IPAddress.c_str());
473 _log.Log(LOG_STATUS, "Logitech Media Server: Player '%s' IP changed to MacAddress", Name.c_str());
474 return;
475 }
476
477 //Player not found, add it...
478 m_sql.safe_query("INSERT INTO WOLNodes (HardwareID, Name, MacAddress, Timeout) VALUES (%d, '%q', '%q', 0)", m_HwdID, Name.c_str(), MacAddress.c_str());
479 _log.Log(LOG_STATUS, "Logitech Media Server: New Player '%s' added", Name.c_str());
480
481 result = m_sql.safe_query("SELECT ID FROM WOLNodes WHERE (HardwareID==%d) AND (MacAddress='%q')", m_HwdID, MacAddress.c_str());
482 if (result.empty())
483 return;
484
485 int ID = atoi(result[0][0].c_str());
486
487 char szID[40];
488 sprintf(szID, "%X%02X%02X%02X", 0, 0, (ID & 0xFF00) >> 8, ID & 0xFF);
489
490 //Also add a light (push) device
491 m_sql.InsertDevice(m_HwdID, szID, 1, pTypeLighting2, sTypeAC, STYPE_Media, 0, "Unavailable", Name, 12, 255, 1);
492
493 ReloadNodes();
494 }
495
SetSettings(const int PollIntervalsec)496 void CLogitechMediaServer::SetSettings(const int PollIntervalsec)
497 {
498 //Defaults
499 m_iPollInterval = 30;
500
501 if (PollIntervalsec > 1)
502 m_iPollInterval = PollIntervalsec;
503 }
504
WriteToHardware(const char * pdata,const unsigned char)505 bool CLogitechMediaServer::WriteToHardware(const char *pdata, const unsigned char /*length*/)
506 {
507 const tRBUF *pSen = reinterpret_cast<const tRBUF*>(pdata);
508
509 unsigned char packettype = pSen->ICMND.packettype;
510
511 if (packettype != pTypeLighting2)
512 return false;
513
514 long DevID = (pSen->LIGHTING2.id3 << 8) | pSen->LIGHTING2.id4;
515 std::vector<LogitechMediaServerNode>::const_iterator itt;
516 for (itt = m_nodes.begin(); itt != m_nodes.end(); ++itt)
517 {
518 if (itt->DevID == DevID)
519 {
520 int iParam = pSen->LIGHTING2.level;
521 std::string sParam;
522 switch (pSen->LIGHTING2.cmnd)
523 {
524 case light2_sOn:
525 case light2_sGroupOn:
526 return SendCommand(itt->ID, "PowerOn");
527 case light2_sOff:
528 case light2_sGroupOff:
529 return SendCommand(itt->ID, "PowerOff");
530 case gswitch_sPlay:
531 SendCommand(itt->ID, "NowPlaying");
532 return SendCommand(itt->ID, "Play");
533 case gswitch_sPlayPlaylist:
534 sParam = GetPlaylistByRefID(iParam);
535 return SendCommand(itt->ID, "PlayPlaylist", sParam);
536 case gswitch_sPlayFavorites:
537 return SendCommand(itt->ID, "PlayFavorites");
538 case gswitch_sStop:
539 return SendCommand(itt->ID, "Stop");
540 case gswitch_sPause:
541 return SendCommand(itt->ID, "Pause");
542 case gswitch_sSetVolume:
543 sParam = std::to_string(iParam);
544 return SendCommand(itt->ID, "SetVolume", sParam);
545 default:
546 return true;
547 }
548 }
549 }
550
551 return false;
552 }
553
ReloadNodes()554 void CLogitechMediaServer::ReloadNodes()
555 {
556 m_nodes.clear();
557 std::vector<std::vector<std::string> > result;
558 result = m_sql.safe_query("SELECT ID,Name,MacAddress FROM WOLNodes WHERE (HardwareID==%d)", m_HwdID);
559 if (!result.empty())
560 {
561 _log.Log(LOG_STATUS, "Logitech Media Server: %d player-switch(es) found.", (int)result.size());
562 std::vector<std::vector<std::string> >::const_iterator itt;
563 for (itt = result.begin(); itt != result.end(); ++itt)
564 {
565 std::vector<std::string> sd = *itt;
566
567 LogitechMediaServerNode pnode;
568 pnode.ID = 0;
569 pnode.DevID = atoi(sd[0].c_str());
570 sprintf(pnode.szDevID, "%X%02X%02X%02X", 0, 0, (pnode.DevID & 0xFF00) >> 8, pnode.DevID & 0xFF);
571 pnode.Name = sd[1];
572 pnode.IP = sd[2];
573 pnode.nStatus = MSTAT_UNKNOWN;
574 pnode.sStatus = "";
575 pnode.LastOK = mytime(NULL);
576
577 std::vector<std::vector<std::string> > result2;
578 result2 = m_sql.safe_query("SELECT ID,nValue,sValue FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%q') AND (Unit == 1)", m_HwdID, pnode.szDevID);
579 if (result2.size() == 1)
580 {
581 pnode.ID = atoi(result2[0][0].c_str());
582 pnode.nStatus = (_eMediaStatus)atoi(result2[0][1].c_str());
583 pnode.sStatus = result2[0][2];
584 }
585
586 m_nodes.push_back(pnode);
587 }
588 }
589 else
590 _log.Log(LOG_ERROR, "Logitech Media Server: No player-switches found.");
591 }
592
ReloadPlaylists()593 void CLogitechMediaServer::ReloadPlaylists()
594 {
595 m_playlists.clear();
596
597 std::string sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"\",[\"playlists\",0,999]]}";
598 Json::Value root = Query(m_IP, m_Port, sPostdata);
599
600 if (root.isNull()) {
601 _log.Log(LOG_ERROR, "Logitech Media Server: No response from server %s:%i", m_IP.c_str(), m_Port);
602 }
603 else {
604 int totPlaylists = root["count"].asInt();
605 if (totPlaylists > 0) {
606 _log.Log(LOG_STATUS, "Logitech Media Server: %i playlist(s) found.", totPlaylists);
607 for (int ii = 0; ii < totPlaylists; ii++)
608 {
609 LMSPlaylistNode pnode;
610
611 pnode.ID = root["playlists_loop"][ii]["id"].asInt();
612 pnode.Name = root["playlists_loop"][ii]["playlist"].asString();
613 pnode.refID = pnode.ID % 256;
614
615 m_playlists.push_back(pnode);
616 }
617 }
618 else
619 _log.Log(LOG_STATUS, "Logitech Media Server: No playlists found.");
620 }
621 }
622
GetPlaylistByRefID(const int ID)623 std::string CLogitechMediaServer::GetPlaylistByRefID(const int ID)
624 {
625 std::vector<CLogitechMediaServer::LMSPlaylistNode>::const_iterator itt;
626
627 for (itt = m_playlists.begin(); itt != m_playlists.end(); ++itt) {
628 if (itt->refID == ID) return itt->Name;
629 }
630
631 _log.Log(LOG_ERROR, "Logitech Media Server: Playlist ID %d not found.", ID);
632 return "";
633 }
634
SendCommand(const int ID,const std::string & command,const std::string & param)635 bool CLogitechMediaServer::SendCommand(const int ID, const std::string &command, const std::string ¶m)
636 {
637 std::vector<std::vector<std::string> > result;
638 std::string sPlayerId = "";
639 std::string sLMSCmnd = "";
640
641 // Get device details
642 result = m_sql.safe_query("SELECT DeviceID FROM DeviceStatus WHERE (ID==%d)", ID);
643 if (result.size() == 1)
644 {
645 // Get connection details
646 long DeviceID = strtol(result[0][0].c_str(), NULL, 16);
647 result = m_sql.safe_query("SELECT Name, MacAddress,Timeout FROM WOLNodes WHERE (HardwareID==%d) AND (ID==%d)", m_HwdID, DeviceID);
648 sPlayerId = result[0][1];
649 }
650
651 if (result.size() == 1)
652 {
653 //std::string sLMSCall;
654 if (command == "Left") {
655 sLMSCmnd = "\"button\", \"arrow_left\"";
656 }
657 else if (command == "Right") {
658 sLMSCmnd = "\"button\", \"arrow_right\"";
659 }
660 else if (command == "Up") {
661 sLMSCmnd = "\"button\", \"arrow_up\"";
662 }
663 else if (command == "Down") {
664 sLMSCmnd = "\"button\", \"arrow_down\"";
665 }
666 else if (command == "Favorites") {
667 sLMSCmnd = "\"button\", \"favorites\"";
668 }
669 else if (command == "Browse") {
670 sLMSCmnd = "\"button\", \"browse\"";
671 }
672 else if (command == "NowPlaying") {
673 sLMSCmnd = "\"button\", \"playdisp_toggle\"";
674 }
675 else if (command == "Shuffle") {
676 sLMSCmnd = "\"button\", \"shuffle_toggle\"";
677 }
678 else if (command == "Repeat") {
679 sLMSCmnd = "\"button\", \"repeat_toggle\"";
680 }
681 else if (command == "Stop") {
682 sLMSCmnd = "\"button\", \"stop\"";
683 }
684 else if (command == "VolumeUp") {
685 sLMSCmnd = "\"mixer\", \"volume\", \"+2\"";
686 }
687 else if (command == "Mute") {
688 sLMSCmnd = "\"mixer\", \"muting\", \"toggle\"";
689 }
690 else if (command == "VolumeDown") {
691 sLMSCmnd = "\"mixer\", \"volume\", \"-2\"";
692 }
693 else if (command == "Rewind") {
694 sLMSCmnd = "\"button\", \"rew.single\"";
695 }
696 else if (command == "Play") {
697 sLMSCmnd = "\"button\", \"play.single\"";
698 }
699 else if (command == "PlayPlaylist") {
700 if (param == "") return false;
701 sLMSCmnd = "\"playlist\", \"play\", \"" + param + "\"";
702 }
703 else if (command == "PlayFavorites") {
704 sLMSCmnd = "\"favorites\", \"playlist\", \"play\"";
705 }
706 else if (command == "Pause") {
707 sLMSCmnd = "\"button\", \"pause.single\"";
708 }
709 else if (command == "Forward") {
710 sLMSCmnd = "\"button\", \"fwd.single\"";
711 }
712 else if (command == "PowerOn") {
713 sLMSCmnd = "\"power\", \"1\"";
714 }
715 else if (command == "PowerOff") {
716 sLMSCmnd = "\"power\", \"0\"";
717 }
718 else if (command == "SetVolume") {
719 if (param == "") return false;
720 sLMSCmnd = "\"mixer\", \"volume\", \"" + param + "\"";
721 }
722
723 if (sLMSCmnd != "")
724 {
725 std::string sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + sPlayerId + "\",[" + sLMSCmnd + "]]}";
726 Json::Value root = Query(m_IP, m_Port, sPostdata);
727
728 sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + sPlayerId + "\",[\"status\",\"-\",1,\"tags:uB\"]]}";
729 root = Query(m_IP, m_Port, sPostdata);
730
731 if (root["player_connected"].asString() == "1")
732 {
733 std::string sPower = root["power"].asString();
734 std::string sMode = root["mode"].asString();
735
736 if (command == "Stop")
737 return sMode == "stop";
738 else if (command == "Pause")
739 return sMode == "pause";
740 else if (command == "Play")
741 return sMode == "play";
742 else if (command == "PlayPlaylist")
743 return sMode == "play";
744 else if (command == "PlayFavorites")
745 return sMode == "play";
746 else if (command == "PowerOn")
747 return sPower == "1";
748 else if (command == "PowerOff")
749 return sPower == "0";
750 else
751 return false;
752 }
753 else
754 return false;
755 }
756 else
757 {
758 _log.Log(LOG_ERROR, "Logitech Media Server: (%s) Command: '%s'. Unknown command.", result[0][0].c_str(), command.c_str());
759 return false;
760 }
761 }
762 else
763 {
764 _log.Log(LOG_ERROR, "Logitech Media Server: (%d) Command: '%s'. Device not found.", ID, command.c_str());
765 return false;
766 }
767 }
768
SendText(const std::string & playerIP,const std::string & subject,const std::string & text,const int duration)769 void CLogitechMediaServer::SendText(const std::string &playerIP, const std::string &subject, const std::string &text, const int duration)
770 {
771 if ((playerIP != "") && (text != "") && (duration > 0))
772 {
773 std::string sLine1 = subject;
774 std::string sLine2 = text;
775 std::string sFont = ""; //"huge";
776 std::string sBrightness = "4";
777 std::string sDuration = std::to_string(duration);
778 std::string sPostdata = "{\"id\":1,\"method\":\"slim.request\",\"params\":[\"" + playerIP + "\",[\"show\",\"line1:" + sLine1 + "\",\"line2:" + sLine2 + "\",\"duration:" + sDuration + "\",\"brightness:" + sBrightness + "\",\"font:" + sFont + "\"]]}";
779 Json::Value root = Query(m_IP, m_Port, sPostdata);
780 }
781 }
782
GetPlaylists()783 std::vector<CLogitechMediaServer::LMSPlaylistNode> CLogitechMediaServer::GetPlaylists()
784 {
785 return m_playlists;
786 }
787
GetPlaylistRefID(const std::string & name)788 int CLogitechMediaServer::GetPlaylistRefID(const std::string &name)
789 {
790 std::vector<CLogitechMediaServer::LMSPlaylistNode>::const_iterator itt;
791
792 for (itt = m_playlists.begin(); itt != m_playlists.end(); ++itt) {
793 if (itt->Name == name) return itt->refID;
794 }
795
796 _log.Log(LOG_ERROR, "Logitech Media Server: Playlist '%s' not found.", name.c_str());
797 return 0;
798 }
799
800 //Webserver helpers
801 namespace http {
802 namespace server {
Cmd_LMSSetMode(WebEmSession & session,const request & req,Json::Value & root)803 void CWebServer::Cmd_LMSSetMode(WebEmSession & session, const request& req, Json::Value &root)
804 {
805 if (session.rights != 2)
806 {
807 session.reply_status = reply::forbidden;
808 return; //Only admin user allowed
809 }
810 std::string hwid = request::findValue(&req, "idx");
811 std::string mode1 = request::findValue(&req, "mode1");
812 if (
813 (hwid == "") ||
814 (mode1 == "")
815 )
816 return;
817 int iHardwareID = atoi(hwid.c_str());
818 CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardware(iHardwareID);
819 if (pBaseHardware == NULL)
820 return;
821 if (pBaseHardware->HwdType != HTYPE_LogitechMediaServer)
822 return;
823 CLogitechMediaServer *pHardware = reinterpret_cast<CLogitechMediaServer*>(pBaseHardware);
824
825 root["status"] = "OK";
826 root["title"] = "LMSSetMode";
827
828 int iMode1 = atoi(mode1.c_str());
829
830 m_sql.safe_query("UPDATE Hardware SET Mode1=%d WHERE (ID == '%q')", iMode1, hwid.c_str());
831 pHardware->SetSettings(iMode1);
832 pHardware->Restart();
833 }
834
Cmd_LMSDeleteUnusedDevices(WebEmSession & session,const request & req,Json::Value &)835 void CWebServer::Cmd_LMSDeleteUnusedDevices(WebEmSession & session, const request& req, Json::Value &/*root*/)
836 {
837 if (session.rights != 2)
838 {
839 session.reply_status = reply::forbidden;
840 return; //Only admin user allowed
841 }
842 std::string hwid = request::findValue(&req, "idx");
843 if (hwid == "")
844 return;
845 int iHardwareID = atoi(hwid.c_str());
846 CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardware(iHardwareID);
847 if (pBaseHardware == NULL)
848 return;
849 if (pBaseHardware->HwdType != HTYPE_LogitechMediaServer)
850 return;
851 m_sql.safe_query("DELETE FROM WOLNodes WHERE ((HardwareID==%d) AND (Timeout==-1))", iHardwareID);
852 }
853
Cmd_LMSGetNodes(WebEmSession & session,const request & req,Json::Value & root)854 void CWebServer::Cmd_LMSGetNodes(WebEmSession & session, const request& req, Json::Value &root)
855 {
856 if (session.rights != 2)
857 {
858 session.reply_status = reply::forbidden;
859 return; //Only admin user allowed
860 }
861 std::string hwid = request::findValue(&req, "idx");
862 if (hwid == "")
863 return;
864 int iHardwareID = atoi(hwid.c_str());
865 CDomoticzHardwareBase *pHardware = m_mainworker.GetHardware(iHardwareID);
866 if (pHardware == NULL)
867 return;
868 if (pHardware->HwdType != HTYPE_LogitechMediaServer)
869 return;
870
871 root["status"] = "OK";
872 root["title"] = "LMSGetNodes";
873
874 std::vector<std::vector<std::string> > result;
875 result = m_sql.safe_query("SELECT ID,Name,MacAddress,(CASE Timeout WHEN -1 THEN 'Unused' ELSE 'Active' END) as Status FROM WOLNodes WHERE (HardwareID==%d)", iHardwareID);
876 if (!result.empty())
877 {
878 std::vector<std::vector<std::string> >::const_iterator itt;
879 int ii = 0;
880 for (itt = result.begin(); itt != result.end(); ++itt)
881 {
882 std::vector<std::string> sd = *itt;
883
884 root["result"][ii]["idx"] = sd[0];
885 root["result"][ii]["Name"] = sd[1];
886 root["result"][ii]["Mac"] = sd[2];
887 root["result"][ii]["Status"] = sd[3];
888 ii++;
889 }
890 }
891 }
892
Cmd_LMSGetPlaylists(WebEmSession &,const request & req,Json::Value & root)893 void CWebServer::Cmd_LMSGetPlaylists(WebEmSession & /*session*/, const request& req, Json::Value &root)
894 {
895 std::string hwid = request::findValue(&req, "idx");
896 if (hwid == "")
897 return;
898 int iHardwareID = atoi(hwid.c_str());
899 CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardware(iHardwareID);
900 if (pBaseHardware == NULL)
901 return;
902 if (pBaseHardware->HwdType != HTYPE_LogitechMediaServer)
903 return;
904 CLogitechMediaServer *pHardware = reinterpret_cast<CLogitechMediaServer*>(pBaseHardware);
905
906 root["status"] = "OK";
907 root["title"] = "LMSGetPlaylists";
908
909 std::vector<CLogitechMediaServer::LMSPlaylistNode> _nodes = pHardware->GetPlaylists();
910
911 int ii = 0;
912 for (const auto & itt : _nodes)
913 {
914 root["result"][ii]["id"] = itt.ID;
915 root["result"][ii]["refid"] = itt.refID;
916 root["result"][ii]["Name"] = itt.Name;
917 ii++;
918 }
919 }
920
Cmd_LMSMediaCommand(WebEmSession &,const request & req,Json::Value & root)921 void CWebServer::Cmd_LMSMediaCommand(WebEmSession & /*session*/, const request& req, Json::Value &root)
922 {
923 std::string sIdx = request::findValue(&req, "idx");
924 std::string sAction = request::findValue(&req, "action");
925 if (sIdx.empty())
926 return;
927 int idx = atoi(sIdx.c_str());
928 root["status"] = "OK";
929 root["title"] = "LMSMediaCommand";
930
931 std::vector<std::vector<std::string> > result;
932 result = m_sql.safe_query("SELECT DS.SwitchType, H.Type, H.ID FROM DeviceStatus DS, Hardware H WHERE (DS.ID=='%q') AND (DS.HardwareID == H.ID)", sIdx.c_str());
933 if (result.size() == 1)
934 {
935 _eSwitchType sType = (_eSwitchType)atoi(result[0][0].c_str());
936 _eHardwareTypes hType = (_eHardwareTypes)atoi(result[0][1].c_str());
937 int HwID = atoi(result[0][2].c_str());
938 // Is the device a media Player?
939 if (sType == STYPE_Media)
940 {
941 switch (hType) {
942 case HTYPE_LogitechMediaServer:
943 CLogitechMediaServer LMS(HwID);
944 LMS.SendCommand(idx, sAction);
945 break;
946 // put other players here ...
947 }
948 }
949 }
950 }
951
952 }
953 }
954