From 86a4fe7e28952f969609cfb52afae06b40aa273e Mon Sep 17 00:00:00 2001
From: David Woodhouse <dwmw2@infradead.org>
Date: Sat, 12 May 2018 15:11:29 +0100
Subject: [PATCH] Add generic CustomCommand handling for scripts

The Onkyo-specific support in JSON and dzVents isn't really sufficient,
because it (quite rightly) needs authentication. But dzVents just assumes
it can make unauthenticated calls to localhost:8080, and doesn't work.

I've been working around this by spawning an external script to invoke
curl, but *that* only works over HTTPS not HTTP for some reason (without
HTTPS I just get an 'Unauthorized' response). And that takes literally
*seconds* to work, on the slow machine I'm using.

Fix it up so that the script command arrays can have a 'CustomCommand',
done generically so that it can easily cover devices other than Onkyo.
---
 .../runtime/device-adapters/onkyo_device.lua  |  4 +-
 hardware/DomoticzHardware.cpp                 |  5 ++
 hardware/DomoticzHardware.h                   |  1 +
 hardware/OnkyoAVTCP.cpp                       |  4 ++
 hardware/OnkyoAVTCP.h                         |  1 +
 main/EventSystem.cpp                          | 57 +++++++++++++++++++
 main/EventSystem.h                            |  1 +
 main/SQLHelper.cpp                            |  4 ++
 main/SQLHelper.h                              | 15 ++++-
 9 files changed, 88 insertions(+), 4 deletions(-)

diff --git a/dzVents/runtime/device-adapters/onkyo_device.lua b/dzVents/runtime/device-adapters/onkyo_device.lua
index ec74841f..8222fe6b 100644
--- a/dzVents/runtime/device-adapters/onkyo_device.lua
+++ b/dzVents/runtime/device-adapters/onkyo_device.lua
@@ -17,9 +17,7 @@ return {
     process = function (device, data, domoticz, utils, adapterManager)
 
         function device.onkyoEISCPCommand(cmd)
-            local url = domoticz.settings['Domoticz url'] ..
-                    '/json.htm?param=onkyoeiscpcommand&type=command&idx=' .. device.id .. '&action=' .. tostring (cmd)
-            return domoticz.openURL(url)
+            return TimedCommand(domoticz, 'CustomCommand:' .. device.id, tostring(cmd), 'device')
         end
     end
 }
diff --git a/hardware/DomoticzHardware.cpp b/hardware/DomoticzHardware.cpp
index 7aad167b..75db53c0 100644
--- a/hardware/DomoticzHardware.cpp
+++ b/hardware/DomoticzHardware.cpp
@@ -36,6 +36,11 @@ CDomoticzHardwareBase::~CDomoticzHardwareBase()
 {
 }
 
+bool CDomoticzHardwareBase::CustomCommand(const uint64_t idx, const std::string &sCommand)
+{
+	return false;
+}
+
 bool CDomoticzHardwareBase::Start()
 {
 	m_iHBCounter = 0;
diff --git a/hardware/DomoticzHardware.h b/hardware/DomoticzHardware.h
index a8790f91..660fc2de 100644
--- a/hardware/DomoticzHardware.h
+++ b/hardware/DomoticzHardware.h
@@ -16,6 +16,7 @@ public:
 	bool Start();
 	bool Stop();
 	virtual bool WriteToHardware(const char *pdata, const unsigned char length)=0;
+	virtual bool CustomCommand(const uint64_t idx, const std::string &sCommand);
 
 	void EnableOutputLog(const bool bEnableLog);
 
diff --git a/hardware/OnkyoAVTCP.cpp b/hardware/OnkyoAVTCP.cpp
index 88766032..9f386c01 100644
--- a/hardware/OnkyoAVTCP.cpp
+++ b/hardware/OnkyoAVTCP.cpp
@@ -676,6 +676,10 @@ void OnkyoAVTCP::ParseData(const unsigned char *pData, int Len)
 	m_pPartialPkt = new_partial;
 }
 
+bool OnkyoAVTCP::CustomCommand(const uint64_t idx, const std::string &sCommand)
+{
+	return SendPacket(sCommand.c_str());
+}
 
 //Webserver helpers
 namespace http {
diff --git a/hardware/OnkyoAVTCP.h b/hardware/OnkyoAVTCP.h
index 90dbce51..133f4876 100644
--- a/hardware/OnkyoAVTCP.h
+++ b/hardware/OnkyoAVTCP.h
@@ -21,6 +21,7 @@ private:
 	int m_retrycntr;
 	bool StartHardware();
 	bool StopHardware();
+	bool CustomCommand(uint64_t idx, const std::string &sCommand);
 	unsigned char *m_pPartialPkt;
 	int m_PPktLen;
 	void ReceiveMessage(const char *pData, int Len);
diff --git a/main/EventSystem.cpp b/main/EventSystem.cpp
index cdd3968b..19562d37 100644
--- a/main/EventSystem.cpp
+++ b/main/EventSystem.cpp
@@ -2481,6 +2481,21 @@ bool CEventSystem::parseBlocklyActions(const _tEventItem &item)
 			actionsDone = true;
 			continue;
 		}
+		else if (deviceName.find("CustomCommand:") == 0)
+		{
+			int idx = atoi(deviceName.substr(14).c_str());
+			float afterTimerSeconds = 0;
+			size_t aFind = doWhat.find(" AFTER ");
+			if ((aFind > 0) && (aFind != std::string::npos)) {
+				std::string delayString = doWhat.substr(aFind + 7);
+				afterTimerSeconds = static_cast<float>(atof(delayString.c_str()));
+				doWhat = doWhat.substr(0, aFind);
+				StripQuotes(doWhat);
+			}
+			m_sql.AddTaskItem(_tTaskItem::CustomCommand(afterTimerSeconds, idx, doWhat));
+			actionsDone = true;
+			continue;
+		}
 		else
 		{
 			_log.Log(LOG_ERROR, "EventSystem: Unknown action sequence! (%s)", csubstr.c_str());
@@ -2623,6 +2638,19 @@ bool CEventSystem::PythonScheduleEvent(std::string ID, const std::string &Action
 				return false;
 		}
 
+		return true;
+	} else if(ID.find("CustomCommand:") == 0) {
+		int idx = atoi(ID.substr(14).c_str());
+		std::string doWhat = std::string(Action);
+		float afterTimerSeconds = 0;
+		size_t aFind = Action.find(" AFTER ");
+		if ((aFind > 0) && (aFind != std::string::npos)) {
+			std::string delayString = doWhat.substr(aFind + 7);
+			doWhat = doWhat.substr(0, aFind);
+			afterTimerSeconds = static_cast<float>(atof(delayString.c_str()));
+			StripQuotes(doWhat);
+		}
+		m_sql.AddTaskItem(_tTaskItem::CustomCommand(afterTimerSeconds, idx, doWhat));
 		return true;
 	}
 	return ScheduleEvent(ID, Action,eventName);
@@ -3540,6 +3568,20 @@ bool CEventSystem::processLuaCommand(lua_State *lua_state, const std::string &fi
 			return false;
 		}
 	}
+	else if (lCommand.find("CustomCommand:") == 0)
+	{
+		int idx = atoi(lCommand.substr(14).c_str());
+		std::string luaString = lua_tostring(lua_state, -1);
+		float afterTimerSeconds = 0;
+		size_t aFind = luaString.find(" AFTER ");
+		if ((aFind > 0) && (aFind != std::string::npos)) {
+			std::string delayString = luaString.substr(aFind + 7);
+			afterTimerSeconds = static_cast<float>(atof(delayString.c_str()));
+			luaString = luaString.substr(0, aFind);
+			StripQuotes(luaString);
+		}
+		m_sql.AddTaskItem(_tTaskItem::CustomCommand(afterTimerSeconds, idx, luaString));
+	}
 	else
 	{
 		if (ScheduleEvent(lua_tostring(lua_state, -2), lua_tostring(lua_state, -1), filename)) {
@@ -3557,6 +3599,21 @@ void CEventSystem::report_errors(lua_State *L, int status, std::string filename)
 	}
 }
 
+bool CEventSystem::CustomCommand(const uint64_t idx, const std::string &sCommand)
+{
+	std::vector<std::vector<std::string> > result;
+	result = m_sql.safe_query("SELECT H.ID FROM DeviceStatus DS, Hardware H WHERE (DS.ID=='%u') AND (DS.HardwareID == H.ID)", idx);
+	if (result.size() != 1)
+		return false;
+
+	int HardwareID = atoi(result[0][0].c_str());
+	CDomoticzHardwareBase *pHardware = m_mainworker.GetHardware(HardwareID);
+	if (!pHardware)
+		return false;
+
+	return pHardware->CustomCommand(idx, sCommand);
+}
+
 void CEventSystem::UpdateDevice(const uint64_t idx, const int nValue, const std::string &sValue, const int Protected, const bool bEventTrigger)
 {
 	//Get device parameters
diff --git a/main/EventSystem.h b/main/EventSystem.h
index d154c4e8..cfebe2cc 100644
--- a/main/EventSystem.h
+++ b/main/EventSystem.h
@@ -131,6 +131,7 @@ public:
 	bool GetEventTrigger(const uint64_t ulDevID, const _eReason reason, const bool bEventTrigger);
 	void SetEventTrigger(const uint64_t ulDevID, const _eReason reason, const float fDelayTime);
 	void UpdateDevice(const uint64_t idx, const int nValue, const std::string &sValue, const int Protected, const bool bEventTrigger = false);
+	bool CustomCommand(const uint64_t idx, const std::string &sCommand);
 
 	void TriggerURL(const std::string &result, const std::vector<std::string> &headerData, const std::string &callback);
 
diff --git a/main/SQLHelper.cpp b/main/SQLHelper.cpp
index 7a46f021..0ea71d54 100644
--- a/main/SQLHelper.cpp
+++ b/main/SQLHelper.cpp
@@ -3401,6 +3401,10 @@ void CSQLHelper::Do_Work()
 			{
 				m_mainworker.m_eventsystem.UpdateDevice(itt->_idx, itt->_nValue, itt->_sValue, itt->_HardwareID, (itt->_switchtype ? true : false));
 			}
+			else if (itt->_ItemType == TITEM_CUSTOM_COMMAND)
+			{
+				m_mainworker.m_eventsystem.CustomCommand(itt->_idx, itt->_command);
+			}
 
 			++itt;
 		}
diff --git a/main/SQLHelper.h b/main/SQLHelper.h
index c0d40580..8ad60e3d 100644
--- a/main/SQLHelper.h
+++ b/main/SQLHelper.h
@@ -49,7 +49,8 @@ enum _eTaskItemType
 	TITEM_SEND_NOTIFICATION,
 	TITEM_SET_SETPOINT,
 	TITEM_SEND_IFTTT_TRIGGER,
-	TITEM_UPDATEDEVICE
+	TITEM_UPDATEDEVICE,
+	TITEM_CUSTOM_COMMAND,
 };
 
 struct _tTaskItem
@@ -261,6 +262,18 @@ struct _tTaskItem
 		tItem._sValue = varvalue;
 		tItem._command = mode;
 		tItem._sUntil = until;
+
+		if (DelayTime)
+			getclock(&tItem._DelayTimeBegin);
+		return tItem;
+	}
+	static _tTaskItem CustomCommand(const float DelayTime, const uint64_t idx, const std::string &cmdstr)
+	{
+		_tTaskItem tItem;
+		tItem._ItemType = TITEM_CUSTOM_COMMAND;
+		tItem._DelayTime = DelayTime;
+		tItem._idx = idx;
+		tItem._command = cmdstr;
 		if (DelayTime)
 			getclock(&tItem._DelayTimeBegin);
 		return tItem;
-- 
2.17.0