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¶m=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¶m=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¶m=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¶m=deletehardware&idx=2 252 local url = "type=command¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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¶m=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