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, &ltime);
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 &param)
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