1 #include "stdafx.h"
2 #include "OnkyoAVTCP.h"
3 #include "../main/Logger.h"
4 #include "../main/Helper.h"
5 #include "../main/SQLHelper.h"
6 #include <iostream>
7 #include "../main/localtime_r.h"
8 #include "../main/mainworker.h"
9 #include "../hardware/hardwaretypes.h"
10 #include <json/json.h>
11 #include "../tinyxpath/tinyxml.h"
12 #include "../main/WebServer.h"
13 
14 #include <sstream>
15 
16 #define RETRY_DELAY 30
17 
18 
19 struct eiscp_packet {
20 	uint32_t magic;
21 	uint32_t hdr_size;
22 	uint32_t data_size;
23 	uint8_t version;
24 	uint8_t reserved[3];
25 	char start;
26 	char dest;
27 	char message[2];
28 };
29 
30 // Do not change except to append to these, or you break compatibility.
31 enum DevNr {
32 	ID_MVL = 0,
33 	ID_ZVL,
34 	ID_VL3,
35 	ID_VL4,
36 	ID_PWR,
37 	ID_ZPW,
38 	ID_PW3,
39 	ID_PW4,
40 	ID_SLI,
41 	ID_SLZ,
42 	ID_SL3,
43 	ID_SL4,
44 	ID_HDO,
45 };
46 
47 struct selector_name {
48 	int val;
49 	const char *name;
50 };
51 
52 static struct selector_name input_names[] = {
53 	{ 0x00, "STB/DVR" },
54 	{ 0x01, "CBL/SAT" },
55 	{ 0x02, "GAME/TV" },
56 	{ 0x03, "AUX1" },
57 	{ 0x04, "AUX2/GAME2" },
58 	{ 0x05, "PC" },
59 	{ 0x06, "VIDEO7" },
60 	{ 0x07, "Hidden1" },
61 	{ 0x08, "Hidden2" },
62 	{ 0x09, "Hidden3" },
63 	{ 0x10, "BD/DVD" },
64 	{ 0x11, "STRM BOX" },
65 	{ 0x12, "TV" },
66 	{ 0x20, "TV/TAPE" },
67 	{ 0x21, "TAPE2" },
68 	{ 0x22, "PHONO" },
69 	{ 0x23, "TV/CD" },
70 	{ 0x24, "FM" },
71 	{ 0x25, "AM" },
72 	{ 0x26, "TUNER" },
73 	{ 0x27, "Music Server" },
74 	{ 0x28, "Internet Radio" },
75 	{ 0x29, "USB/USB(Front)" },
76 	{ 0x2a, "USB(Rear)" },
77 	{ 0x2c, "USB(Toggle)" },
78 	{ 0x2d, "Airplay" },
79 	{ 0x2e, "Bluetooth" },
80 	{ 0x30, "MULTI CH" },
81 	{ 0x31, "XM" },
82 	{ 0x32, "SIRIUS" },
83 	{ 0x33, "DAB" },
84 	{ 0x40, "Universal PORT" },
85 	{ 0x55, "HDMI5" },
86 	{ 0x56, "HDMI6" },
87 	{ 0x57, "HDMI7" },
88 	{ 0x80, "Source" },
89 	{ 0, NULL },
90 };
91 
92 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
93 static struct {
94 	const char *iscpCmd;
95 	const char *iscpMute;
96 	const char *name;
97 	uint8_t switchType;
98 	uint8_t subtype;
99 	int customImage;
100 	const char *options;
101 	struct selector_name *default_names;
102 } switch_types[] = {
103 	{ "MVL", "AMT", "Master volume", STYPE_Dimmer, sSwitchGeneralSwitch, 8, NULL, NULL },
104 	{ "ZVL", "ZMT", "Zone 2 volume", STYPE_Dimmer, sSwitchGeneralSwitch, 8, NULL, NULL },
105 	{ "VL3", "MT3", "Zone 3 volume", STYPE_Dimmer, sSwitchGeneralSwitch, 8, NULL, NULL },
106 	{ "VL4", "MT4", "Zone 4 volume", STYPE_Dimmer, sSwitchGeneralSwitch, 8, NULL, NULL },
107 	{ "PWR", NULL, "Master power", STYPE_OnOff, sSwitchGeneralSwitch, 5, NULL, NULL },
108 	{ "ZPW", NULL, "Zone 2 power", STYPE_OnOff, sSwitchGeneralSwitch, 5, NULL, NULL },
109 	{ "PW3", NULL, "Zone 3 power", STYPE_OnOff, sSwitchGeneralSwitch, 5, NULL, NULL },
110 	{ "PW4", NULL, "Zone 4 power", STYPE_OnOff, sSwitchGeneralSwitch, 5, NULL, NULL },
111 	{ "SLI", NULL, "Master selector", STYPE_Selector, sSwitchTypeSelector, 5, "SelectorStyle:1;LevelNames:Off;LevelOffHidden:true;LevelActions:00", input_names },
112 	{ "SLZ", NULL, "Zone 2 selector", STYPE_Selector, sSwitchTypeSelector, 5, "SelectorStyle:1;LevelNames:Off;LevelOffHidden:true;LevelActions:00", input_names },
113 	{ "SL2", NULL, "Zone 3 selector", STYPE_Selector, sSwitchTypeSelector, 5, "SelectorStyle:1;LevelNames:Off;LevelOffHidden:true;LevelActions:00", input_names },
114 	{ "SL3", NULL, "Zone 4 selector", STYPE_Selector, sSwitchTypeSelector, 5, "SelectorStyle:1;LevelNames:Off;LevelOffHidden:true;LevelActions:00", input_names },
115 	{ "HDO", NULL, "HDMI Output", STYPE_Selector, sSwitchTypeSelector, 5, "SelectorStyle:0;LevelNames:Off|Main|Sub|Main+Sub;LevelOffHidden:true;LevelActions:00|01|02|03" },
116 };
117 
118 static struct {
119 	const char *iscpCmd;
120 	const char *name;
121 } text_types[] = {
122 	{ "NAL", "Album name" },
123 	{ "NAT", "Artist name" },
124 	{ "NTI", "Title name" },
125 	{ "NTM", "Playback time" },
126 	{ "NTR", "Track info" },
127 };
128 
129 #define IS_END_CHAR(x) ((x) == 0x1a)
130 
131 
OnkyoAVTCP(const int ID,const std::string & IPAddress,const unsigned short usIPPort)132 OnkyoAVTCP::OnkyoAVTCP(const int ID, const std::string &IPAddress, const unsigned short usIPPort) :
133 	m_szIPAddress(IPAddress)
134 {
135 	m_HwdID=ID;
136 	m_usIPPort=usIPPort;
137 	m_retrycntr = RETRY_DELAY;
138 	m_pPartialPkt = NULL;
139 	m_PPktLen = 0;
140 
141 	// Ooops, changing Device ID was a mistake. Fix up migration for Main/Z2 power switches from
142 	// the 3.8153 release. Don't change IDs again!
143 	for (int i = ID_PWR; i < ID_PW3; i++)
144 		m_sql.safe_query("UPDATE DeviceStatus SET DeviceID='%08X' WHERE (HardwareID==%d) AND (DeviceID=='%08X') AND (SwitchType==%d) AND (SubType==%d)",
145 				 i, m_HwdID, i - 2, switch_types[i].switchType, switch_types[i].subtype);
146 }
147 
~OnkyoAVTCP(void)148 OnkyoAVTCP::~OnkyoAVTCP(void)
149 {
150 	free(m_pPartialPkt);
151 }
152 
StartHardware()153 bool OnkyoAVTCP::StartHardware()
154 {
155 	RequestStart();
156 
157 	//force connect the next first time
158 	m_retrycntr=RETRY_DELAY;
159 	m_bIsStarted=true;
160 
161 	//Start worker thread
162 	m_thread = std::make_shared<std::thread>(&OnkyoAVTCP::Do_Work, this);
163 	SetThreadNameInt(m_thread->native_handle());
164 	return (m_thread != nullptr);
165 }
166 
StopHardware()167 bool OnkyoAVTCP::StopHardware()
168 {
169 	if (m_thread)
170 	{
171 		RequestStop();
172 		m_thread->join();
173 		m_thread.reset();
174 	}
175 	m_bIsStarted=false;
176 	return true;
177 }
178 
OnConnect()179 void OnkyoAVTCP::OnConnect()
180 {
181 	_log.Log(LOG_STATUS,"OnkyoAVTCP: connected to: %s:%d", m_szIPAddress.c_str(), m_usIPPort);
182 	m_bIsStarted=true;
183 
184 	SendPacket("NRIQSTN");
185 	sOnConnected(this);
186 }
187 
OnDisconnect()188 void OnkyoAVTCP::OnDisconnect()
189 {
190 	_log.Log(LOG_STATUS,"OnkyoAVTCP: disconnected");
191 }
192 
Do_Work()193 void OnkyoAVTCP::Do_Work()
194 {
195 	int sec_counter = 0;
196 	connect(m_szIPAddress, m_usIPPort);
197 	while (!IsStopRequested(1000))
198 	{
199 		sec_counter++;
200 
201 		if (sec_counter  % 12 == 0) {
202 			m_LastHeartbeat = mytime(NULL);
203 		}
204 	}
205 	terminate();
206 
207 	_log.Log(LOG_STATUS,"OnkyoAVTCP: TCP/IP Worker stopped...");
208 }
209 
OnData(const unsigned char * pData,size_t length)210 void OnkyoAVTCP::OnData(const unsigned char *pData, size_t length)
211 {
212 	ParseData(pData,length);
213 }
214 
OnError(const boost::system::error_code & error)215 void OnkyoAVTCP::OnError(const boost::system::error_code& error)
216 {
217 	if (
218 		(error == boost::asio::error::address_in_use) ||
219 		(error == boost::asio::error::connection_refused) ||
220 		(error == boost::asio::error::access_denied) ||
221 		(error == boost::asio::error::host_unreachable) ||
222 		(error == boost::asio::error::timed_out)
223 		)
224 	{
225 		_log.Log(LOG_ERROR, "OnkyoAVTCP: Can not connect to: %s:%d", m_szIPAddress.c_str(), m_usIPPort);
226 	}
227 	else if (
228 		(error == boost::asio::error::eof) ||
229 		(error == boost::asio::error::connection_reset)
230 		)
231 	{
232 		_log.Log(LOG_STATUS, "OnkyoAVTCP: Connection reset!");
233 	}
234 	else
235 		_log.Log(LOG_ERROR, "OnkyoAVTCP: %s", error.message().c_str());
236 }
237 
WriteToHardware(const char * pdata,const unsigned char)238 bool OnkyoAVTCP::WriteToHardware(const char *pdata, const unsigned char /*length*/)
239 {
240 	if (!isConnected() || !pdata)
241 	{
242 		return false;
243 	}
244 	const tRBUF *pCmd = reinterpret_cast<const tRBUF *>(pdata);
245 	unsigned char packettype = pCmd->ICMND.packettype;
246 	std::string message = "";
247 
248 	if (packettype == pTypeGeneralSwitch) {
249 		const _tGeneralSwitch *xcmd = reinterpret_cast<const _tGeneralSwitch*>(pdata);
250 		int ID = xcmd->id;
251 		int level = xcmd->level;
252 		char buf[9];
253 		if (ID >= ARRAY_SIZE(switch_types))
254 			return false;
255 		switch(xcmd->cmnd) {
256 		case gswitch_sSetLevel:
257 			if (switch_types[ID].subtype == sSwitchTypeSelector) {
258 				std::vector<std::vector<std::string> > result;
259 				result = m_sql.safe_query("SELECT Options FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%08X')", m_HwdID, ID);
260 				if (result.empty()) {
261 					_log.Log(LOG_ERROR, "OnkyoAVTCP: No device with ID %08X", ID);
262 					return false;
263 				}
264 				std::string val = GetSelectorSwitchLevelAction(m_sql.BuildDeviceOptions(result[0][0]), level);
265 				if (val.empty())
266 					snprintf(buf, 6, "%s%02X", switch_types[ID].iscpCmd, level / 10);
267 				else {
268 					// Seriously. They *send* us the ID with lower case, and then refuse to accept it like that.
269 					boost::to_upper(val);
270 					snprintf(buf, 6, "%s%s", switch_types[ID].iscpCmd, val.c_str());
271 				}
272 			} else {
273 				snprintf(buf, 6, "%s%02X", switch_types[ID].iscpCmd, level);
274 			}
275 			SendPacket(buf);
276 			break;
277 		case gswitch_sOn:
278 			if (switch_types[ID].iscpMute)
279 				sprintf(buf, "%s00", switch_types[ID].iscpMute);
280 			else
281 				sprintf(buf, "%s01", switch_types[ID].iscpCmd);
282 			SendPacket(buf);
283 			break;
284 		case gswitch_sOff:
285 			if (switch_types[ID].iscpMute)
286 				sprintf(buf, "%s01", switch_types[ID].iscpMute);
287 			else
288 				sprintf(buf, "%s00", switch_types[ID].iscpCmd);
289 			SendPacket(buf);
290 			break;
291 		}
292 	}
293 	return true;
294 }
295 
SendPacket(const char * pdata)296 bool OnkyoAVTCP::SendPacket(const char *pdata)
297 {
298 	if (!isConnected() || !pdata)
299 	{
300 		return false;
301 	}
302 
303 	_log.Log(LOG_NORM, "OnkyoAVTCP: Send %s", pdata);
304 	size_t length = strlen(pdata);
305 
306 	struct eiscp_packet *pPkt = (struct eiscp_packet *)malloc(sizeof(*pPkt) + length);
307 	if (!pPkt)
308 	{
309 		return false;
310 	}
311 
312 	pPkt->magic = htonl(0x49534350); // "ISCP"
313 	pPkt->hdr_size = htonl(16);
314 	pPkt->data_size = htonl(length + 3);
315 	pPkt->version = 1;
316 	memset(pPkt->reserved, 0, sizeof(pPkt->reserved));
317 	pPkt->start = '!';
318 	pPkt->dest = '1';
319 	memcpy(pPkt->message, pdata, length);
320 	pPkt->message[length] = 0x0d;
321 	pPkt->message[length + 1] = 0x0a;
322 
323 	write((unsigned char *)pPkt, length + sizeof(*pPkt));
324 	free(pPkt);
325 	return true;
326 }
327 
SendPacket(const char * pCmd,const char * pArg)328 bool OnkyoAVTCP::SendPacket(const char *pCmd, const char *pArg)
329 {
330 	std::string str(pCmd);
331 	str += pArg;
332 
333 	return SendPacket(str.c_str());
334 }
335 
ReceiveSwitchMsg(const char * pData,int Len,bool muting,int ID)336 void OnkyoAVTCP::ReceiveSwitchMsg(const char *pData, int Len, bool muting, int ID)
337 {
338 	char *endp;
339 	int level = strtol(pData + 3, &endp, 16);
340 	int action = gswitch_sSetLevel;
341 
342 	if (!IS_END_CHAR(*endp))
343 		return;
344 
345 	if (muting)
346 		action = level ? gswitch_sOff : gswitch_sOn;
347 	else if (switch_types[ID].switchType == STYPE_OnOff)
348 		action = level ? gswitch_sOn : gswitch_sOff;
349 
350 
351 	std::vector<std::vector<std::string> > result;
352 	result = m_sql.safe_query("SELECT Name,nValue,sValue,Options,ID FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%08X') AND (Unit == %d)",
353 				  m_HwdID, ID, 0);
354 	if (result.empty()) {
355 		EnsureSwitchDevice(ID, NULL);
356 		result = m_sql.safe_query("SELECT Name,nValue,sValue,Options,ID FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%08X') AND (Unit == %d)",
357 					  m_HwdID, ID, 0);
358 		if (result.empty())
359 			return;
360 	}
361 
362 	if (switch_types[ID].subtype == sSwitchTypeSelector) {
363 		std::map<std::string, std::string> options = m_sql.BuildDeviceOptions(result[0][3]);
364 		std::map<std::string, std::string>::const_iterator itt = options.find("LevelActions");
365 		if (itt == options.end())
366 			return;
367 		std::string sOptions = itt->second;
368 		std::vector<std::string> strarray;
369 		boost::split(strarray, sOptions, boost::is_any_of("|"), boost::token_compress_off);
370 		std::vector<std::string>::iterator itt2;
371 		int i = 0;
372 		for (itt2 = strarray.begin(); itt2 != strarray.end(); ++itt2) {
373 			if (strtoul(itt2->c_str(), NULL, 16) == (unsigned long)level)
374 				break;
375 			i += 10;
376 		}
377 		if (itt2 == strarray.end()) {
378 			// Add previously unknown value to a selector
379 			std::string str(reinterpret_cast<const char*>(pData + 3), Len - 4);
380 			std::string level_name = str;
381 			struct selector_name *n = switch_types[ID].default_names;
382 			while (n && n->name) {
383 				if (n->val == level) {
384 					level_name = n->name;
385 					break;
386 				}
387 				n++;
388 			}
389 			options["LevelActions"] = options["LevelActions"] + "|" + str;
390 			options["LevelNames"] = options["LevelNames"] + "|" + level_name;
391 			m_sql.SetDeviceOptions(atoi(result[0][4].c_str()), options);
392 		}
393 		level = i;
394 	}
395 	if (!result.empty() && action == gswitch_sSetLevel) {
396 		//check if we have a change, if not do not update it
397 		int nvalue = atoi(result[0][1].c_str());
398 		if ((!level) && (nvalue == gswitch_sOff))
399 			return;
400 		if ((level && (nvalue != gswitch_sOff))) {
401 			//Check Level
402 			int slevel = atoi(result[0][2].c_str());
403 			if (slevel == level)
404 				return;
405 		}
406 	}
407 
408 	_tGeneralSwitch gswitch;
409 	gswitch.subtype = switch_types[ID].subtype;
410 	gswitch.id = ID;
411 	gswitch.unitcode = 0;
412 	gswitch.cmnd = (uint8_t)action;
413 	gswitch.level = (uint8_t)level;
414 	gswitch.battery_level = 255;
415 	gswitch.rssi = 12;
416 	gswitch.seqnbr = 0;
417 
418 	sDecodeRXMessage(this, (const unsigned char *)&gswitch, switch_types[ID].name, 255);
419 }
420 
EnsureSwitchDevice(int ID,const char * options)421 void OnkyoAVTCP::EnsureSwitchDevice(int ID, const char *options)
422 {
423 	std::vector<std::vector<std::string> > result;
424 	std::string options_str;
425 	result = m_sql.safe_query("SELECT ID FROM DeviceStatus WHERE (HardwareID==%d) AND (DeviceID=='%08X')", m_HwdID, ID);
426 	if (result.empty()) {
427 		if (!options && switch_types[ID].options) {
428 			options_str = m_sql.FormatDeviceOptions(m_sql.BuildDeviceOptions(switch_types[ID].options, false));
429 			options = options_str.c_str();
430 			_log.Log(LOG_ERROR, "Options from '%s': '%s'\n", switch_types[ID].options, options);
431 		}
432 		m_sql.safe_query(
433 				 "INSERT INTO DeviceStatus (HardwareID, DeviceID, Unit, Type, SubType, SwitchType, Used, SignalLevel, BatteryLevel, Name, nValue, sValue, CustomImage, Options) "
434 				 "VALUES (%d, '%08X', %d, %d, %d, %d, %d, 12, 255, '%q', 0, '%q', %d, '%q')",
435 				 m_HwdID, ID, 0, pTypeGeneralSwitch, switch_types[ID].subtype, switch_types[ID].switchType, 1, switch_types[ID].name, "", switch_types[ID].customImage, options?options:"");
436 
437 	}
438 }
439 
440 
BuildSelectorOptions(const std::string & names,const std::string & ids)441 std::string OnkyoAVTCP::BuildSelectorOptions(const std::string & names, const std::string & ids)
442 {
443 	std::map<std::string, std::string> optionsMap;
444 	optionsMap.insert(std::pair<std::string, std::string>("LevelOffHidden", "true"));
445 	optionsMap.insert(std::pair<std::string, std::string>("SelectorStyle", "1"));
446 	optionsMap.insert(std::pair<std::string, std::string>("LevelNames", names));
447 	optionsMap.insert(std::pair<std::string, std::string>("LevelActions", ids));
448 	return m_sql.FormatDeviceOptions(optionsMap);
449 }
450 
ReceiveXML(const char * pData,int Len)451 bool OnkyoAVTCP::ReceiveXML(const char *pData, int Len)
452 {
453 	TiXmlDocument doc;
454 
455 	((char *)pData)[Len - 1] = 0;
456 	pData += 3;
457 
458 	doc.Parse(pData);
459 	if (doc.Error()) {
460 		_log.Log(LOG_ERROR, "OnkyoAVTCP: Failed to parse NRIQSTN result: %s", doc.ErrorDesc());
461 		// XX: Fallback, add defaults anyway? */
462 		return false;
463 	}
464 	TiXmlElement *pResponseNode = doc.FirstChildElement("response");
465 	if (!pResponseNode) {
466 		_log.Log(LOG_ERROR, "No <response> element in NRIQSTN result");
467 		return false;
468 	}
469 	TiXmlElement *pDeviceNode = pResponseNode->FirstChildElement("device");
470 	if (!pDeviceNode) {
471 		_log.Log(LOG_ERROR, "No <device> element in NRIQSTN result");
472 		return false;
473 	}
474 	std::string InputNames[4], InputIDs[4];
475 	TiXmlElement *pSelectorlistNode = pDeviceNode->FirstChildElement("selectorlist");
476 	if (pSelectorlistNode) {
477 		TiXmlElement *pSelector;
478 		for ( pSelector = pSelectorlistNode->FirstChildElement(); pSelector;
479 		      pSelector = pSelector->NextSiblingElement() ) {
480 			const char *id = pSelector->Attribute("id");
481 			const char *name = pSelector->Attribute("name");
482 			const char *zone = pSelector->Attribute("zone");
483 			int value;
484 			if (!id || !name || !zone ||
485 			    pSelector->QueryIntAttribute("value", &value) != TIXML_SUCCESS ||
486 			    !value)
487 				continue;
488 
489 			char *escaped_name = strdup(name);
490 			int nlen = strlen(name);
491 			while (nlen) {
492 				if (escaped_name[nlen - 1] == ' ' && escaped_name[nlen] == 0)
493 					escaped_name[nlen - 1] = 0;
494 				else if (escaped_name[nlen - 1] == '|')
495 					escaped_name[nlen - 1] = '!';
496 				nlen--;
497 			}
498 			if (!escaped_name[0])
499 				continue;
500 
501 			for (int i = 0; i < 3; i++) {
502 				int zone_nr = strtoul(zone, NULL, 16);
503 				if (zone_nr & (1 << i)) {
504 					if (InputNames[i].empty())
505 						InputNames[i] = "Off";
506 					InputNames[i] += '|';
507 					InputNames[i] += escaped_name;
508 					if (InputIDs[i].empty())
509 						InputIDs[i] = "Off";
510 					InputIDs[i] += '|';
511 					InputIDs[i] += id;
512 				}
513 			}
514 			free(escaped_name);
515 		}
516 	}
517 	TiXmlElement *pZonelistNode = pDeviceNode->FirstChildElement("zonelist");
518 	if (pZonelistNode) {
519 		TiXmlElement *pZone;
520 		for ( pZone = pZonelistNode->FirstChildElement(); pZone;
521 		      pZone = pZone->NextSiblingElement() ) {
522 			int id, value, volmax, volstep;
523 			const char *name = pZone->Attribute("name");
524 
525 			if (!name)
526 				continue;
527 
528 			if (pZone->QueryIntAttribute("value", &value) != TIXML_SUCCESS ||
529 			    pZone->QueryIntAttribute("id", &id) != TIXML_SUCCESS ||
530 			    pZone->QueryIntAttribute("volmax", &volmax) != TIXML_SUCCESS ||
531 			    pZone->QueryIntAttribute("volstep", &volstep) != TIXML_SUCCESS)
532 				continue;
533 
534 			if (!value)
535 				continue;
536 
537 			if (id < 1 || id > 4)
538 				continue;
539 
540 			// Create the input selector immediately, with the known options.
541 			std::string options = BuildSelectorOptions(InputNames[id - 1], InputIDs[id - 1]);;
542 			EnsureSwitchDevice(ID_SLI + id - 1, options.c_str());
543 
544 			// Send queries for it, and the power and volume for this zone.
545 			SendPacket(switch_types[ID_SLI + id - 1].iscpCmd, "QSTN");
546 			SendPacket(switch_types[ID_PWR + id - 1].iscpCmd, "QSTN");
547 			SendPacket(switch_types[ID_MVL + id - 1].iscpCmd, "QSTN");
548 		}
549 	}
550 
551 	SendPacket("HDOQSTN");
552 	return true;
553 }
554 
ReceiveMessage(const char * pData,int Len)555 void OnkyoAVTCP::ReceiveMessage(const char *pData, int Len)
556 {
557 	while (Len && (pData[Len - 1] == '\r' || pData[Len - 1] == '\n'))
558 		Len--;
559 
560 	if (Len < 4 || !IS_END_CHAR(pData[Len-1]))
561 		return;
562 
563 	// Special-case the startup XML data
564 	if (!memcmp(pData, "NRI", 3)) {
565 		ReceiveXML(pData, Len);
566 		return;
567 	}
568 	_log.Log(LOG_NORM, "OnkyoAVTCP: Packet received: %d %.*s", Len, Len-1, pData);
569 
570 	int i;
571 	for (i = 0; i < ARRAY_SIZE(text_types); i++) {
572 		if (!memcmp(pData, text_types[i].iscpCmd, 3)) {
573 			std::string str(reinterpret_cast<const char*>(pData + 3), Len - 4);
574 			SendTextSensor(1, i, 255, str, text_types[i].name);
575 			return;
576 		}
577 	}
578 
579 	for (i = 0; i < ARRAY_SIZE(switch_types); i++) {
580 	  if (!memcmp(pData, switch_types[i].iscpCmd, 3)) {
581 		  ReceiveSwitchMsg(pData, Len, false, i);
582 		  return;
583 	  } else if (switch_types[i].iscpMute && !memcmp(pData, switch_types[i].iscpMute, 3)) {
584 		  ReceiveSwitchMsg(pData, Len, true, i);
585 		  return;
586 	  }
587 	}
588 }
589 
ParseData(const unsigned char * pData,int Len)590 void OnkyoAVTCP::ParseData(const unsigned char *pData, int Len)
591 {
592 	if (m_pPartialPkt) {
593 		unsigned char *new_data = (unsigned char *)realloc(m_pPartialPkt, m_PPktLen + Len);
594 		if (!new_data) {
595 			free(m_pPartialPkt);
596 			m_pPartialPkt = NULL;
597 			m_PPktLen = 0;
598 			_log.Log(LOG_ERROR, "OnkyoAVTCP: Failed to prepend previous data");
599 			// We'll attempt to resync
600 		} else {
601 			memcpy(new_data + m_PPktLen, pData, Len);
602 			m_pPartialPkt = new_data;
603 			Len += m_PPktLen;
604 			pData = new_data;
605 		}
606 	}
607 	while (Len >= 18) {
608 		const struct eiscp_packet *pkt = (const struct eiscp_packet *)pData;
609 		if (pkt->magic != htonl(0x49534350) || // "ISCP"
610 		    pkt->hdr_size != htonl(16) || pkt->version != 1) {
611 			Len--;
612 			pData++;
613 			continue;
614 		}
615 		int data_size = static_cast<int>(ntohl(pkt->data_size));
616 		if (Len < 16 + data_size)
617 			break;
618 
619 		ReceiveMessage(pkt->message, data_size - 2);
620 		Len -= 16 + data_size;
621 		pData += 16 + data_size;
622 	}
623 	unsigned char *new_partial = NULL;
624 	if (Len) {
625 		if (pData == m_pPartialPkt) {
626 			m_PPktLen = Len;
627 			return;
628 		}
629 		new_partial = (unsigned char *)malloc(Len);
630 		if (new_partial)
631 			memcpy(new_partial, pData, Len);
632 		else Len = 0;
633 	}
634 	free(m_pPartialPkt);
635 	m_PPktLen = Len;
636 	m_pPartialPkt = new_partial;
637 }
638 
CustomCommand(const uint64_t,const std::string & sCommand)639 bool OnkyoAVTCP::CustomCommand(const uint64_t /*idx*/, const std::string &sCommand)
640 {
641 	return SendPacket(sCommand.c_str());
642 }
643 
644 //Webserver helpers
645 namespace http {
646 	namespace server {
Cmd_OnkyoEiscpCommand(WebEmSession & session,const request & req,Json::Value & root)647 		void CWebServer::Cmd_OnkyoEiscpCommand(WebEmSession & session, const request& req, Json::Value &root)
648 		{
649 			if (session.rights != 2)
650 			{
651 				session.reply_status = reply::forbidden;
652 				return; //Only admin user allowed
653 			}
654 
655 			std::string sIdx = request::findValue(&req, "idx");
656 			std::string sAction = request::findValue(&req, "action");
657 			if (sIdx.empty())
658 				return;
659 			//int idx = atoi(sIdx.c_str());
660 
661 			std::vector<std::vector<std::string> > result;
662 			result = m_sql.safe_query("SELECT DS.SwitchType, DS.DeviceID, H.Type, H.ID FROM DeviceStatus DS, Hardware H WHERE (DS.ID=='%q') AND (DS.HardwareID == H.ID)", sIdx.c_str());
663 
664 			root["status"] = "ERR";
665 			if (result.size() == 1)
666 			{
667 				_eHardwareTypes	hType = (_eHardwareTypes)atoi(result[0][2].c_str());
668 				switch (hType) {
669 				// We allow raw EISCP commands to be sent on *any* of the logical devices
670 				// associated with the hardware.
671 				case HTYPE_OnkyoAVTCP:
672 					CDomoticzHardwareBase *pBaseHardware = m_mainworker.GetHardwareByIDType(result[0][3].c_str(), HTYPE_OnkyoAVTCP);
673 					if (pBaseHardware == NULL)
674 						return;
675 					OnkyoAVTCP *pOnkyoAVTCP = reinterpret_cast<OnkyoAVTCP*>(pBaseHardware);
676 
677 					pOnkyoAVTCP->SendPacket(sAction.c_str());
678 					root["status"] = "OK";
679 					root["title"] = "OnkyoEiscpCommand";
680 					break;
681 				}
682 			}
683 		}
684 
685 	}
686 }
687