1package.path = package.path ..
2		";../?.lua;../device-adapters/?.lua;./data/?.lua;../../../scripts/dzVents/generated_scripts/?.lua;" ..
3		"../../../scripts/lua/?.lua"
4
5local _ = require 'lodash'
6local File = require('File')
7local http = require("socket.http")
8
9local ltn12 = require("ltn12")
10local jsonParser = require('JSON')
11local scriptSourcePath = './'
12local scriptTargetPath = '../../../scripts/dzVents/scripts/'
13local generatedScriptTargetPath = '../../../scripts/dzVents/generated_scripts/'
14local dataTargetPath = '../../../scripts/dzVents/data/'
15local DUMMY_HW = 15
16local DUMMY_HW_ID = 2
17
18
19local function DomoticzTestTools(port, debug, webroot)
20
21	if (port == nil) then port = 8080 end
22
23	local BASE_URL = 'http://localhost:' .. port
24
25	if (webroot ~= '' and webroot ~= nil) then
26		BASE_URL = BASE_URL .. '/' .. webroot
27	end
28
29	local API_URL = BASE_URL .. '/json.htm?'
30
31
32	local self = {
33		['port'] = port,
34		url = BASE_URL,
35		apiUrl = API_URL
36	}
37
38	function self.getScriptSourcePath(name)
39		return scriptSourcePath .. name
40	end
41
42	function self.getScriptTargetPath(name)
43		return scriptTargetPath .. name
44	end
45
46	function self.copyScript(name)
47		File.remove(self.getScriptTargetPath(name))
48		File.copy(self.getScriptSourcePath(name), self.getScriptTargetPath(name))
49	end
50
51	function self.removeFSScript(name)
52		os.remove(self.getScriptTargetPath(name))
53	end
54
55	function self.removeGUIScript(name)
56		os.remove(dataTargetPath .. name)
57	end
58
59	function self.removeDataFile(name)
60		os.remove(dataTargetPath .. name)
61	end
62
63	function self.doAPICall(url)
64		local response = {}
65		local _url = self.apiUrl .. url
66
67		local result, respcode, respheaders, respstatus = http.request {
68			method = "GET",
69			url = _url,
70			sink = ltn12.sink.table(response)
71		}
72
73		local _json = table.concat(response)
74
75		local json = jsonParser:decode(_json)
76
77		local ok = json.status == 'OK'
78
79		if (debug and not ok) then
80			_.print('--------------')
81			_.print(_url)
82			_.print(ok)
83			_.print(json)
84			_.print(debug.traceback())
85			_.print('--------------')
86		end
87
88
89		return ok, json, result, respcode, respheaders, respstatus
90	end
91
92	function self.createHardware(name, type)
93		local url = "param=addhardware&type=command&htype=" .. tostring(type) .. "&name=" .. name .. "&enabled=true&datatimeout=0"
94		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
95		local idx = json.idx
96		return ok, idx, json, result, respcode, respheaders, respstatus
97	end
98
99	function self.createDummyHardware()
100		local ok, dummyIdx = self.createHardware('dummy', DUMMY_HW)
101		return ok, dummyIdx
102	end
103
104	function self.createManagedCounter(name)
105		local url = "type=createdevice&idx=" .. DUMMY_HW_ID .."&sensorname=" .. name .. "&sensormappedtype=0xF321"
106		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
107		return ok, json, result, respcode, respheaders, respstatus
108	end
109
110	function self.createCamera()
111		local url = "type=command&param=addcamera&address=192.168.192.123&port=8083&name=camera1&enabled=true&imageurl=aW1hZ2UuanBn&protocol=0"
112		--&username=&password=&imageurl=aW1hZ2UuanBn&protocol=0
113		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
114		return ok, json, result, respcode, respheaders, respstatus
115	end
116
117	function self.createVirtualDevice(hw, name, type, options)
118
119		local url = "type=createvirtualsensor&idx=" .. tostring(hw) .."&sensorname=" .. name .. "&sensortype=" .. tostring(type)
120		if tostring(type) == "33" then
121			url = "type=createdevice&idx=" .. tostring(hw) .."&sensorname=" .. name .. "&sensormappedtype=0xF321"
122		end
123
124		if (options) then
125			url = url .. '&sensoroptions=1;' .. tostring(options)
126		end
127
128		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
129		local idx = json.idx
130
131		return ok, idx, json, result, respcode, respheaders, respstatus
132	end
133
134	function self.createScene(name)
135		-- type=addscene&name=myScene&scenetype=0
136		local url = "type=addscene&name=" .. name .. "&scenetype=0"
137
138		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
139		local idx = json.idx
140		return ok, idx, json, result, respcode, respheaders, respstatus
141	end
142
143	function self.createGroup(name)
144		-- type=addscene&name=myGroup&scenetype=1
145		local url = "type=addscene&name=" .. name .. "&scenetype=1"
146
147		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
148		local idx = json.idx
149
150		return ok, idx, json, result, respcode, respheaders, respstatus
151	end
152
153	function self.createVariable(name, type, value)
154		-- todo, encode value
155		--type=command&param=adduservariable&vname=myint&vtype=0&vvalue=1
156		local url = "param=adduservariable&type=command&vname=" .. name .."&vtype=" .. tostring(type) .. "&vvalue=" .. tostring(value)
157
158		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
159
160		local idx = json.idx
161
162		return ok, idx, json, result, respcode, respheaders, respstatus
163	end
164
165	function self.deleteDevice(idx)
166		-- type=deletedevice&idx=2;1;...
167		local url = "type=deletedevice&idx=" .. tostring(idx)
168		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
169		return ok, json
170	end
171
172	function self.deleteGroupScene(idx)
173		-- type=deletescene&idx=1
174		local url = "type=deletescene&idx=" .. tostring(idx)
175		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
176		return ok, json
177	end
178
179	function self.getDevice(idx)
180		--type=devices&rid=15
181		local url = "type=devices&rid=" .. idx
182		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
183		if (ok and json.result ~= nil and _.size(json.result) == 1) then
184			return ok, json.result[1]
185		end
186		return false, nil
187	end
188
189	function self.getAllDevices()
190		-- type=devices&displayhidden=1&displaydisabled=1&filter=all&used=all
191		local url = "type=devices&displayhidden=1&displaydisabled=1&filter=all&used=all"
192		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
193		return ok, json, result
194	end
195
196	function self.deleteAllDevices()
197		local ok, json, result = self.getAllDevices()
198		local res = true
199		if (ok) then
200			if (_.size(json.result) > 0) then
201				for i, device in pairs(json.result) do
202					local ok, json
203					if (device.Type == 'Scene' or device.Type == 'Group') then
204						ok, json = self.deleteGroupScene(device.idx)
205					else
206						ok, json = self.deleteDevice(device.idx)
207					end
208
209					if (not ok) then
210						print('Failed to remove device: ' .. device.Name)
211						res = false
212					end
213				end
214			end
215		else
216			return false
217		end
218		return res
219	end
220
221	function self.deleteVariable(idx)
222		--type=command&param=deleteuservariable&idx=1
223		local url = "param=deleteuservariable&type=command&idx=" .. tostring(idx)
224		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
225		return ok, json
226	end
227
228	function self.deleteAllVariables()
229		-- type=commandparam=onkyoeiscm=getuservariables
230		local url = "param=getuservariables&type=command"
231		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
232		local res = true
233		if (ok) then
234			if (_.size(json.result) > 0) then
235				for i, variable in pairs(json.result) do
236					local ok, json
237					ok, json = self.deleteVariable(variable.idx)
238					if (not ok) then
239						print('Failed to remove variable: ' .. variable.Name)
240						res = false
241					end
242				end
243			end
244		else
245			return false
246		end
247		return res
248	end
249
250	function self.deleteCamera(idx)
251		-- http://localhost:8080/json.htm?type=command&param=deletehardware&idx=2
252		local url = "type=command&param=deletecamera&idx=" .. tostring(idx)
253		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
254		return ok, json
255	end
256
257	function self.deleteAllCameras()
258		local url = "type=cameras"
259		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
260		local res = true
261		if (ok) then
262			if (_.size(json.result) > 0) then
263				for i, camera in pairs(json.result) do
264					local ok, json = self.deleteCamera(camera.idx)
265					if (not ok) then
266						print('Failed to remove cameras: ' .. camera.Name)
267						res = false
268					end
269				end
270			end
271		else
272			return false
273		end
274		return res
275	end
276
277
278	function self.deleteHardware(idx)
279		-- http://localhost:8080/json.htm?type=command&param=deletehardware&idx=2
280		local url = "param=deletehardware&type=command&idx=" .. tostring(idx)
281		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
282		return ok, json
283	end
284
285	function self.deleteAllHardware()
286		-- http://localhost:8080/json.htm?type=hardware
287		local url = "type=hardware"
288		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
289		local res = true
290		if (ok) then
291			if (_.size(json.result) > 0) then
292				for i, hw in pairs(json.result) do
293					local ok, json = self.deleteHardware(hw.idx)
294					if (not ok) then
295						print('Failed to remove hardware: ' .. hw.Name)
296						res = false
297					end
298				end
299			end
300		else
301			return false
302		end
303		return res
304	end
305
306	function self.deleteScript(idx)
307		--type=events&param=delete&event=1
308		local url = "param=delete&type=events&event=" .. tostring(idx)
309		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
310		return ok, json
311	end
312
313	function self.deleteAllScripts()
314		-- type=events&param=list
315		local url = "param=list&type=events"
316		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
317		local res = true
318		if (ok) then
319			if (_.size(json.result) > 0) then
320				for i, script in pairs(json.result) do
321					local ok, json
322					ok, json = self.deleteScript(script.id)
323
324					if (not ok) then
325						print('Failed to remove script: ' .. script.Name)
326						res = false
327					end
328					return true
329
330				end
331			end
332		else
333			return false
334		end
335		return res
336	end
337
338	function self.updateSwitch(idx, name, description, switchType)
339		--http://localhost:8080/json.htm?type=setused&idx=1&name=vdSwitch&description=des&strparam1=&strparam2=&protected=false&switchtype=0&customimage=0&used=true&addjvalue=0&addjvalue2=0&options=
340		local url = "type=setused&idx=" ..
341				tostring(idx) ..
342				"&name=" .. name ..
343				"&description=" .. description ..
344				"&strparam1=&strparam2=&protected=false&" ..
345				"switchtype=" .. tostring(switchType) .. "&customimage=0&used=true&addjvalue=0&addjvalue2=0&options="
346		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
347		return ok
348	end
349
350	function self.switch(idx, cmd)
351		--type=command&param=switchlight&idx=39&switchcmd=On&level=0&passcode=
352		local url = "param=switchlight&type=command&switchcmd=" .. cmd .. "&level=0&idx=" .. tostring(idx)
353		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
354		return ok
355	end
356
357	function self.dimTo(idx, cmd, level)
358		--type=command&param=switchlight&idx=39&switchcmd=On&level=0&passcode=
359		local url = "param=switchlight&type=command&switchcmd=" .. cmd .. "&level=" .. tostring(level) .. "&idx=" .. tostring(idx)
360		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
361		return ok
362	end
363
364
365	function self.switchSelector(idx, level)
366		--type=command&param=switchlight&idx=39&switchcmd=On&level=0&passcode=
367		local url = "param=switchlight&type=command&switchcmd=Set%20Level&level=" .. tostring(level) .. "&idx=" .. tostring(idx)
368		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
369		return ok
370	end
371
372
373	function self.switchGroup(idx, cmd)
374		--http://localhost:8080/json.htm?type=command&param=switchscene&idx=2&switchcmd=On&passcode=
375		local url = "param=switchscene&type=command&idx=" .. tostring(idx) .. "&switchcmd=" .. cmd .. "&passcode="
376		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
377		return ok
378	end
379
380	function self.addSceneDevice(sceneIdx, devIdx)
381		-- http://localhost:8080/json.htm?type=command&param=addscenedevice&idx=2&isscene=false&devidx=1&command=On&level=100&hue=0&ondelay=&offdelay=
382		local url = "param=addscenedevice&type=command&idx=" .. tostring(sceneIdx) .. "&isscene=false&devidx=" .. tostring(devIdx) .. "&command=On&level=100&hue=0&ondelay=&offdelay="
383		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
384		return ok
385	end
386
387	function self.initSettings(IFTTTEnabled)
388		local IFTTTEnabled = IFTTTEnabled or "%20"
389		local response = {}
390		local reqbody = "Language=en&Themes=default&Title=Domoticz&Latitude=52.278870&Longitude=5.665849&DashboardType=0&AllowWidgetOrdering=on&MobileType=0&WebUserName=&WebPassword=d41d8cd98f00b204e9800998ecf8427e&AuthenticationMethod=0&GuestUser=0&SecPassword=1&SecOnDelay=0&ProtectionPassword=d41d8cd98f00b204e9800998ecf8427e&WebLocalNetworks=127.0.0.1&RemoteSharedPort=6144&WebRemoteProxyIPs=127.0.0.1&checkforupdates=on&ReleaseChannel=0&AcceptNewHardware=on&HideDisabledHardwareSensors=on&MyDomoticzUserId=&MyDomoticzPassword=&EnableTabLights=on&EnableTabScenes=on&EnableTabTemp=on&EnableTabWeather=on&EnableTabUtility=on&EnableTabCustom=on&LightHistoryDays=30&ShortLogDays=1&ProwlAPI=&NMAAPI=&PushbulletAPI=&PushsaferAPI=&PushsaferImage=&PushoverUser=&PushoverAPI=&PushALotAPI=&ClickatellAPI=&ClickatellTo=&IFTTTAPI=thisIsnotTheRealAPI&IFTTTEnabled=" .. IFTTTEnabled ..
391"&HTTPField1=&HTTPField2=&HTTPField3=&HTTPField4=&HTTPTo=&HTTPURL=https%3A%2F%2Fwww.somegateway.com%2Fpushurl.php%3Fusername%3D%23FIELD1%26password%3D%23FIELD2%26apikey%3D%23FIELD3%26from%3D%23FIELD4%26to%3D%23TO%26message%3D%23MESSAGE&HTTPPostData=&HTTPPostContentType=application%2Fjson&HTTPPostHeaders=&KodiIPAddress=224.0.0.1&KodiPort=9777&KodiTimeToLive=5&LmsPlayerMac=&LmsDuration=5&TelegramAPI=&TelegramChat=&NotificationSensorInterval=43200&NotificationSwitchInterval=0&EmailEnabled=on&EmailFrom=&EmailTo=&EmailServer=&EmailPort=25&EmailUsername=&EmailPassword=&UseEmailInNotifications=on&TempUnit=0&DegreeDaysBaseTemperature=18.0&WindUnit=0&WeightUnit=0&EnergyDivider=1000&CostEnergy=0.2149&CostEnergyT2=0.2149&CostEnergyR1=0.0800&CostEnergyR2=0.0800&GasDivider=100&CostGas=0.6218&WaterDivider=100&CostWater=1.6473&ElectricVoltage=230&CM113DisplayType=0&SmartMeterType=0&FloorplanPopupDelay=750&FloorplanAnimateZoom=on&FloorplanShowSensorValues=on&FloorplanShowSceneNames=on&FloorplanRoomColour=Blue&FloorplanActiveOpacity=25&FloorplanInactiveOpacity=5&RandomSpread=15&SensorTimeout=60&BatterLowLevel=0&LogFilter=&LogFileName=&LogLevel=0&DoorbellCommand=0&RaspCamParams=-w+800+-h+600+-t+1&UVCParams=-S80+-B128+-C128+-G80+-x800+-y600+-q100&EnableEventScriptSystem=on&LogEventScriptTrigger=on&DisableDzVentsSystem=on&DzVentsLogLevel=3"
392
393		local url = BASE_URL .. '/storesettings'
394		local result, respcode, respheaders, respstatus = http.request {
395			method = "POST",
396			source = ltn12.source.string(reqbody),
397			url = url,
398			headers = {
399				['Connection'] = 'keep-alive',
400				['Content-Length'] = _.size(reqbody),
401				['Pragma'] = 'no-cache',
402				['Cache-Control'] = 'no-cache',
403				['Origin'] = BASE_URL,
404				['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
405				['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8',
406				['Accept'] = '*/*',
407				['X-Requested-With'] = 'XMLHttpRequest',
408				['DNT'] = '1',
409				['Referer'] = BASE_URL,
410			},
411			sink = ltn12.sink.table(response)
412		}
413
414		local _json = table.concat(response)
415		local ok = (respcode == 200)
416		return ok, result, respcode, respheaders, respstatus
417
418	end
419
420	function self.reset()
421		local ok = true
422		ok = ok and self.deleteAllDevices()
423		ok = ok and self.deleteAllVariables()
424		ok = ok and self.deleteAllHardware()
425		ok = ok and self.deleteAllScripts()
426		ok = ok and self.initSettings()
427		return ok
428	end
429
430	function self.installFSScripts(scripts)
431		for i,script in pairs(scripts) do
432			self.createFSScript(script)
433		end
434	end
435
436	function self.cleanupFSScripts(scripts)
437		for i,script in pairs(scripts) do
438			self.removeFSScript(script)
439			self.removeDataFile('__data_' .. script)
440		end
441	end
442
443	function self.setDisarmed()
444		-- http://localhost:8080/json.htm?type=command&param=setsecstatus&secstatus=0&seccode=c4ca4238a0b923820dcc509a6f75849b
445		local url = "param=setsecstatus&type=command&secstatus=0&seccode=c4ca4238a0b923820dcc509a6f75849b"
446		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
447		return ok
448	end
449
450	function self.addSecurityPanel(idx)
451		local url = "type=setused&idx=" .. tostring(idx) .. "&name=secPanel&used=true&maindeviceidx="
452		local ok, json, result, respcode, respheaders, respstatus = self.doAPICall(url)
453		return ok
454	end
455
456	function self.createScript(name, code)
457		local response = {}
458		local reqbody = 'name=' .. name ..'&' ..
459				'interpreter=dzVents&' ..
460				'eventtype=All&' ..
461				'xml=' .. tostring(code) .. '&' ..
462				--'xml=return%20%7B%0A%09active%20%3D%20false%2C%0A%09on%20%3D%20%7B%0A%09%09timer%20%3D%20%7B%7D%0A%09%7D%2C%0A%09execute%20%3D%20function(domoticz%2C%20device)%0A%09end%0A%7D&' ..
463				'logicarray=&' ..
464				'eventid=&' ..
465				'eventstatus=1&' ..
466				'editortheme=ace%2Ftheme%2Fxcode'
467		local url = BASE_URL .. '/event_create.webem'
468		local result, respcode, respheaders, respstatus = http.request {
469			method = "POST",
470			source = ltn12.source.string(reqbody),
471			url = url,
472			headers = {
473				['Connection'] = 'keep-alive',
474				['Pragma'] = 'no-cache',
475				['Cache-Control'] = 'no-cache',
476				['Content-Length'] = _.size(reqbody),
477				['Origin'] = BASE_URL,
478				['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36',
479				['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8',
480				['Accept'] = '*/*',
481				['X-Requested-With'] = 'XMLHttpRequest',
482				['DNT'] = '1',
483				['Referer'] = BASE_URL,
484			},
485			sink = ltn12.sink.table(response)
486		}
487
488		local _json = table.concat(response)
489		local ok = (respcode == 200)
490
491		return ok, result, respcode, respheaders, respstatus
492	end
493
494	function self.createGUIScriptFromFile(name)
495		local stage1Script = File.read(name)
496		local ok, result, respcode, respheaders, respstatus = self.createScript('stage1', stage1Script)
497		return ok
498	end
499
500	function self.createFSScript(name)
501		self.copyScript(name)
502	end
503
504	function self.tableEntries(t)
505		local count = 0
506		for _ in pairs(t) do count = count + 1 end
507		return count
508	end
509
510	return self
511
512
513end
514
515return DomoticzTestTools
516