fork(2) download
  1. <![CDATA[----------------------------------------------------------------------
  2. -- VARIABLES
  3. ----------------------------------------------------------------------
  4.  
  5. FireplaceCommands = {
  6. ['STATUS'] = "473100000000000000000000003146",
  7. ['ON'] = "473900000000000000000000003946",
  8. ['OFF'] = "473A00000000000000000000003A46",
  9. ['FIND'] = "475000000000000000000000005046",
  10. ['FAN_BOOST_ON'] = "473700000000000000000000003746",
  11. ['FAN_BOOST_OFF'] = "473800000000000000000000003846",
  12. ['FLAME_EFFECT_ON'] = "475600000000000000000000005646",
  13. ['FLAME_EFFECT_OFF'] = "475500000000000000000000005546"
  14. }
  15.  
  16. TemperatureCommands = {
  17. [3] = "475701030000000000000000005B46",
  18. [4] = "475701040000000000000000005C46",
  19. [5] = "475701050000000000000000005D46",
  20. [6] = "475701060000000000000000005E46",
  21. [7] = "475701070000000000000000005F46",
  22. [8] = "475701080000000000000000006046",
  23. [9] = "475701090000000000000000006146",
  24. [10] = "4757010A0000000000000000006246",
  25. [11] = "4757010B0000000000000000006346",
  26. [12] = "4757010C0000000000000000006446",
  27. [13] = "4757010D0000000000000000006546",
  28. [14] = "4757010E0000000000000000006646",
  29. [15] = "4757010F0000000000000000006746",
  30. [16] = "475701100000000000000000006846",
  31. [17] = "475701110000000000000000006946",
  32. [18] = "475701120000000000000000006A46",
  33. [19] = "475701130000000000000000006B46",
  34. [20] = "475701140000000000000000006C46",
  35. [21] = "475701150000000000000000006D46",
  36. [22] = "475701160000000000000000006E46",
  37. [23] = "475701170000000000000000006F46",
  38. [24] = "475701180000000000000000007046",
  39. [25] = "475701190000000000000000007146",
  40. [26] = "4757011A0000000000000000007246",
  41. [27] = "4757011B0000000000000000007346",
  42. [28] = "4757011C0000000000000000007446",
  43. [29] = "4757011D0000000000000000007546",
  44. [30] = "4757011E0000000000000000007646",
  45. [31] = "4757011F0000000000000000007746"
  46. }
  47.  
  48. FireplaceFeedback = {
  49. ['STATUS'] = "0x80",
  50. ['ON'] = "0x8D",
  51. ['OFF'] = "0x8F",
  52. ['FLAME_EFFECT_ON'] = "0x61",
  53. ['FLAME_EFFECT_OFF'] = "0x60",
  54. ['NEW_SET_TEMP_ACK'] = "0x66",
  55. ['I_AM_A_FIRE'] = "0x90"
  56. }
  57.  
  58. currentState = "open"
  59. tempSetpoint = 0
  60. g_deviceDataPollTimer = 0
  61.  
  62. -- These functions are big-endian and take up to 32 bits
  63.  
  64.  
  65. hex2bin = {
  66. ["0"] = "0000",
  67. ["1"] = "0001",
  68. ["2"] = "0010",
  69. ["3"] = "0011",
  70. ["4"] = "0100",
  71. ["5"] = "0101",
  72. ["6"] = "0110",
  73. ["7"] = "0111",
  74. ["8"] = "1000",
  75. ["9"] = "1001",
  76. ["a"] = "1010",
  77. ["b"] = "1011",
  78. ["c"] = "1100",
  79. ["d"] = "1101",
  80. ["e"] = "1110",
  81. ["f"] = "1111"
  82. }
  83.  
  84.  
  85.  
  86. bin2hex = {
  87. ["0000"] = "0",
  88. ["0001"] = "1",
  89. ["0010"] = "2",
  90. ["0011"] = "3",
  91. ["0100"] = "4",
  92. ["0101"] = "5",
  93. ["0110"] = "6",
  94. ["0111"] = "7",
  95. ["1000"] = "8",
  96. ["1001"] = "9",
  97. ["1010"] = "A",
  98. ["1011"] = "B",
  99. ["1100"] = "C",
  100. ["1101"] = "D",
  101. ["1110"] = "E",
  102. ["1111"] = "F"
  103. }
  104.  
  105. function OnTimerExpired(idTimer)
  106. local timer = pullTimer(idTimer)
  107. if (not timer) then dbg("UNKNOWN TIMER FIRED | THROWING AWAY") return end
  108. timer.handler()
  109. end
  110.  
  111. --------------------------------
  112. -- Helpers
  113. --------------------------------
  114. g_timers = {}
  115.  
  116. function startTimer(value, units, callback)
  117. local timerId = C4:AddTimer(tonumber(value), units)
  118. g_timers[timerId] = { handler = callback }
  119. return timerId
  120. end
  121.  
  122. function pullTimer(timerId)
  123. local timer = g_timers[timerId]
  124. g_timers[timerId] = nil
  125. return timer
  126. end
  127.  
  128. function killTimer(timerId)
  129. local timer = g_timers[timerId]
  130. g_timers[timerId] = nil
  131. if tonumber(timerId) > 0 then
  132. C4:KillTimer(timerId)
  133. end
  134. return timer
  135. end
  136.  
  137. -- Hex2Bin
  138. -- Bin2Hex
  139. -- Hex2Dec
  140. -- Dec2Hex
  141. -- Bin2Dec
  142. -- Dec2Bin
  143.  
  144. function Hex2Bin(s) -- s -> hexadecimal string
  145. local ret = ""
  146. local i = 0
  147.  
  148. for i in string.gfind(s, ".") do
  149. i = string.lower(i)
  150. ret = ret..hex2bin[i]
  151. end
  152.  
  153. return ret
  154. end
  155.  
  156.  
  157. function Bin2Hex(s) -- s -> binary string
  158. local l = 0
  159. local h = ""
  160. local b = ""
  161. local rem
  162.  
  163. l = string.len(s)
  164. rem = l % 4
  165. l = l-1
  166. h = ""
  167.  
  168. -- need to prepend zeros to eliminate mod 4
  169. if (rem > 0) then
  170. s = string.rep("0", 4 - rem)..s
  171. end
  172.  
  173. for i = 1, l, 4 do
  174. b = string.sub(s, i, i+3)
  175. h = h..bin2hex[b]
  176. end
  177.  
  178. return h
  179.  
  180. end
  181.  
  182.  
  183. function Bin2Dec(s)
  184. -- s -> binary string
  185. local num = 0
  186. local ex = string.len(s) - 1
  187. local l = 0
  188.  
  189. l = ex + 1
  190. for i = 1, l do
  191. b = string.sub(s, i, i)
  192. if b == "1" then
  193. num = num + 2^ex
  194. end
  195. ex = ex - 1
  196. end
  197.  
  198. return string.format("%u", num)
  199. end
  200.  
  201.  
  202.  
  203. function Dec2Bin(s, num) -- s -> Base10 string
  204. -- num -> string length to extend to
  205. local n
  206.  
  207. if (num == nil) then
  208. n = 0
  209. else
  210. n = num
  211. end
  212.  
  213. s = string.format("%x", s)
  214. s = Hex2Bin(s)
  215.  
  216. while string.len(s) < n do
  217. s = "0"..s
  218. end
  219.  
  220. return s
  221. end
  222.  
  223. function Hex2Dec(s)
  224. -- s -> hexadecimal string
  225. local s = Hex2Bin(s)
  226. return Bin2Dec(s)
  227. end
  228.  
  229. function Dec2Hex(s)
  230. -- s -> Base10 string
  231. s = string.format("%x", s)
  232. return s
  233. end
  234.  
  235. function GenerateChecksum(data, offset, count)
  236. local polynomial = 0x8408
  237. local crc = 0xFFFF
  238. if (count == 0) then
  239. return 0
  240. else
  241. for i = 0, count, 1 do
  242. local current = data[offset + i]
  243. for j = 0, 7 do
  244. if (bit.band(bit.bxor(crc, current),0x1) > 0) then
  245. crc = bit.bxor(bit.rshift(crc,1),polynomial) % 65536
  246.  
  247. else
  248. crc = bit.rshift(crc,1)
  249. end
  250. current = bit.rshift(current,1)
  251. end
  252. end
  253.  
  254. return bit.bnot(crc) % 65536
  255. end
  256. end
  257.  
  258. -------------------------------------------------------------------
  259. --Function Name : OnPropertyChanged(strName)
  260. --Parameters : idTimer(int)
  261. --Description : Function called when property is changed
  262. -------------------------------------------------------------------
  263.  
  264. function OnPropertyChanged(strProperty)
  265. if (strProperty == "Debug Mode") then
  266. if (Properties[strProperty] == "Off") then
  267. print("DEBUG MODE = OFF\r\n")
  268. debugMode = 0
  269. elseif (Properties[strProperty] == "Print") then
  270. print("DEBUG MODE = PRINT\r\n")
  271. debugMode = 1
  272. debugTimer = C4:AddTimer(168,"HOURS",false)
  273. elseif (Properties[strProperty] == "Log") then
  274. print("DEBUG MODE = LOG\r\n")
  275. debugMode = 2
  276. debugTimer = C4:AddTimer(168,"HOURS",false)
  277. elseif (Properties[strProperty] == "Print and Log") then
  278. print("DEBUG MODE = PRINT AND LOG\r\n")
  279. debugMode = 3
  280. debugTimer = C4:AddTimer(168,"HOURS",false)
  281. end
  282. end
  283. if (strProperty == "IP Address") then
  284. OnDriverLateInit()
  285. end
  286.  
  287. --LICENCING CODE GOES UNDER HERE
  288. if(strProperty == "Activation Key") then
  289. -- Licence_CreateTrial()
  290. if(string.len(Properties[strProperty]) > 0) then
  291. Licence_HouselogixActivate()
  292. end
  293. elseif(strProperty == "Control4 MAC Address") then
  294. if(string.lower(Properties[strProperty]) == "uuddlrlrba") then
  295. C4:AllowExecute(true)
  296. C4:UpdateProperty("Driver Information", "Konami Code Detected - Lua Command Window Activated")
  297. else
  298. C4:AllowExecute(false)
  299. C4:UpdateProperty("Driver Information", "")
  300. C4:UpdateProperty("Control4 MAC Address", C4:GetUniqueMAC())
  301. end
  302. end
  303. end
  304.  
  305. -------------------------------------------------------------------
  306. --Function Name : ReceivedFromProxy
  307. --Parameters : idBinding(int), strCommand (string), tParams(table)
  308. --Description : Function called when proxy command is called
  309. -------------------------------------------------------------------
  310.  
  311. function ReceivedFromProxy(idBinding, strCommand, tParams)
  312. if(Licence_Is_Valid()) then
  313. Dbg("ReceivedFromProxy [" .. idBinding .. "] : " .. strCommand)
  314. if (tParams ~= nil) then
  315. for ParamName, ParamValue in pairs(tParams) do
  316. Dbg(ParamName .. " " .. ParamValue)
  317. end
  318. end
  319.  
  320. if(strCommand == "TOGGLE" or strCommand == "CLICK_TOGGLE_BUTTON" or strCommand == "TOGGLE_PRESET") then
  321. if(currentState == "close") then
  322. SendMessage(FireplaceCommands["FLAME_EFFECT_OFF"])
  323. currentState = "open"
  324. else
  325. SendMessage(FireplaceCommands["FLAME_EFFECT_ON"])
  326. currentState = "close"
  327. end
  328. end
  329. if(strCommand == "OPEN") then
  330. SendMessage(FireplaceCommands["FLAME_EFFECT_OFF"])
  331. C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
  332. currentState = "open"
  333. end
  334. if(strCommand == "CLOSE") then
  335. SendMessage(FireplaceCommands["FLAME_EFFECT_ON"])
  336. C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
  337. currentState = "close"
  338. end
  339.  
  340. if (strCommand == "SET_SCALE") then
  341. C4:SendToProxy(5001, "SCALE_CHANGED", {SCALE = "FAHRENHEIT"}) --only works with fahrenheit
  342. C4:UpdateProperty("Current Scale", "FAHRENHEIT")
  343. end
  344. if (strCommand == "SET_MODE_FAN") then
  345. if(tParams["MODE"] == "AUTO" or tParams["MODE"] == "Auto") then
  346. SendMessage(FireplaceCommands['FAN_BOOST_OFF'])
  347. else
  348. SendMessage(FireplaceCommands['FAN_BOOST_ON'])
  349. end
  350. end
  351. if (strCommand == "SET_MODE_HVAC") then
  352. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", tParams)
  353. C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = tParams["MODE"]})
  354. if(tParams["MODE"] == "OFF" or tParams["MODE"] == "Off") then
  355. SendMessage(FireplaceCommands["OFF"])
  356. else
  357. SendMessage(FireplaceCommands["ON"])
  358. end
  359. elseif (strCommand == "SET_SETPOINT_HEAT") then
  360. C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tParams["SETPOINT"], SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
  361. SendMessage(TemperatureCommands[tonumber(tParams["SETPOINT"])])
  362. tempSetpoint = tonumber(tParams["SETPOINT"])
  363. elseif (strCommand == "SET_POINT") then
  364. if(tonumber(setpoint) > 0) then
  365. SendMessage(TemperatureCommands[tonumber(tParams["SETPOINT"])])
  366. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = mode})
  367. C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tParams["SETPOINT"], SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
  368. else
  369. SendMessage(FireplaceCommands["OFF"])
  370. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = mode})
  371. end
  372. tempSetpoint = tonumber(tParams["SETPOINT"])
  373. end
  374. else
  375. print("There is no active licence or trial licence. This driver has been disabled")
  376. end
  377. end
  378.  
  379. function StatusPlease()
  380. SendMessage(FireplaceCommands["STATUS"])
  381. end
  382.  
  383. function SendMessage(TX_MESSAGE)
  384. createUDPServer()
  385. message = tohex(TX_MESSAGE)
  386. C4:CreateNetworkConnection (6001, Properties['IP Address'])
  387. C4:NetConnect(6001, 3300, "UDP")
  388. end
  389.  
  390.  
  391. function OnDriverUpdate()
  392. OnDriverLateInit()
  393. end
  394.  
  395. function OnDriverUpdated()
  396. OnDriverLateInit()
  397. end
  398.  
  399. function PollData()
  400. Dbg("PollData")
  401. local curTemp = C4:GetDeviceVariable(928,1130)
  402. C4:UpdateProperty("Current Temperature", curTemp .. " DEGREES")
  403. C4:SendToProxy(5001, "TEMPERATURE_CHANGED", {TEMPERATURE = tonumber(curTemp), SCALE = "F"})
  404. killTimer(g_deviceDataPollTimer)
  405. g_deviceDataPollTimer = startTimer(15, "SECONDS",
  406. function()
  407. PollData()
  408. end)
  409. end
  410. -------------------------------------------------------------------
  411. --Function Name : OnDriverLateInit()
  412. --Parameters :
  413. --Description : Function called after properties has initialised
  414. -------------------------------------------------------------------
  415.  
  416. function OnDriverLateInit()
  417. --if (recreateServer ~= nil) then recreateServer = C4:KillTimer(recreateServer) end
  418. --recreateServer = C4:AddTimer(10, "MINUTES", true)
  419. if (status_Timer ~= nil) then status_Timer = C4:KillTimer(status_Timer) end
  420. status_Timer = C4:AddTimer(math.random(30,90), "SECONDS", true)
  421. createUDPServerTimer = C4:AddTimer(60, "SECONDS", false)
  422. C4:SendToProxy(5001, "SCALE_CHANGED", {SCALE = "FAHRENHEIT"})
  423. g_deviceDataPollTimer = startTimer(60, "SECONDS", function() PollData() end)
  424. Dbg("OnDriverLateInit")
  425. --LICENCING CODE GOES UNDER HERE
  426. if((houselogixLicence == nil) or (houselogixLicence == 0)) then houselogixLicence = C4:AddTimer("24","HOURS",true) end
  427. if((startupTimer == nil) or (startupTimer == 0)) then startupTimer = C4:AddTimer("2","MINUTES",false) end
  428. -- Licence_CreateTrial()
  429. C4:UpdateProperty("Driver Version", SOFTWARE_VERSION)
  430.  
  431. startTimer(10, 'SECONDS', function()
  432. -- Extras
  433. SendExtrasSetupToProxy()
  434. SendExtrasStateToProxy(0)
  435. end)
  436. end
  437.  
  438.  
  439. function createUDPServer()
  440. UDPServer = socket.udp()
  441. UDPServer:setsockname("*", 3300)
  442. UDPServer:settimeout(0)
  443.  
  444. Dbg("Escea: UDP Server Created\r\n")
  445.  
  446. --if (UDPPollTimer) then UDPPollTimer = C4:KillTimer(UDPPollTimer) end
  447. UDPPollTimer = C4:AddTimer(3, "SECONDS", false)
  448. Dbg("UDP Server:Timer Created\r\n")
  449. end
  450.  
  451. -------------------------------------------------------------------
  452. --Function Name : OnTimerExpired()
  453. --Parameters : idTimer (int)
  454. --Description : Function to check expired timers
  455. -------------------------------------------------------------------
  456.  
  457. function OnTimerExpired(idTimer)
  458. if (idTimer == createUDPServerTimer) then
  459. --createUDPServer()
  460. end
  461. if (idTimer == UDPPollTimer) then
  462. OnReceivedData()
  463. end
  464. if idTimer == recreateServer then
  465. --UDPServer:close()
  466. --createUDPServer()
  467. end
  468. if (idTimer == status_Timer) then --Poll the fireplace for status feedback
  469. StatusPlease()
  470. end
  471. if (idTimer == debugTimer) then
  472. Dbg("Debug Logs left on for a week. Turning Off")
  473. C4:UpdateProperty("Debug Mode", "Off")
  474. end
  475. --LICENCING CODE GOES UNDER HERE
  476. if(idTimer == trialLicenceTimer) then
  477. Licence_ProcessTrialTimer()
  478. elseif(idTimer == startupTimer) then
  479. Licence_HouselogixActivate()
  480. elseif(idTimer == houselogixLicence) then
  481. Licence_HouselogixActivate()
  482. end
  483. end
  484.  
  485. -------------------------------------------------------------------
  486. --Function Name : OnConnectionStatusChanged(idBinding, nPort, strStatus)
  487. --Parameters : idBinding(int), nPort(int), strStatus(string)
  488. --Description : Function called when socket connect connected/disconnected
  489. -------------------------------------------------------------------
  490.  
  491. function OnConnectionStatusChanged(idBinding, nPort, strStatus)
  492. Dbg("idBinding = " .. idBinding .. "\r\nnPort = " .. nPort .. "\r\nstrStatus = " .. strStatus)
  493. if (strStatus == "ONLINE") then
  494. C4:SendToNetwork(6001, 3300, message)
  495. Dbg("Sending message: ")
  496. hexdump(message)
  497. end
  498. end
  499.  
  500. -------------------------------------------------------------------
  501. --Function Name : ReceivedFromNetwork
  502. --Parameters : idBinding(int), nPort(int), strData(string)
  503. --Description : Function called when data received on socket connection
  504. -------------------------------------------------------------------
  505.  
  506. function OnReceivedData()
  507. local Data,ip = UDPServer:receivefrom()
  508. if string.find(ip,Properties['IP Address']) then
  509. elseif(string.find(ip,"refused")) then
  510. else
  511. Dbg("Data received from different IP Address: " .. ip)
  512. return
  513. end
  514. if(Licence_Is_Valid()) then
  515. if Data ~= nil then
  516. Dbg("Receiving message (" .. ip .. "):")
  517. hexdump(Data)
  518. local packet = {}
  519. for i = 0, 14 do
  520. packet[i] = tonumber(Hex2Dec(returnByte(string.byte(Data,i+1))))
  521. end
  522. if((packet[0] == 0x47) and (packet[14] == 0x46)) then
  523. Dbg("Received message")
  524. if(packet[1] == 0x80) then --STATUS
  525. ProcessStatus(packet)
  526. elseif(packet[1] == 0x90) then --I AM A FIRE
  527. ProcessFind(packet)
  528. elseif(packet[1] == 0x39) then --POWER ON
  529. C4:UpdateProperty("Current Mode", "ON")
  530. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Heat"})
  531. C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Heat"})
  532. Dbg("POWER = ON")
  533. elseif(packet[1] == 0x3A) then --POWER OFF
  534. C4:UpdateProperty("Current Mode", "OFF")
  535. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Off"})
  536. C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Off"})
  537. Dbg("POWER = OFF")
  538. elseif(packet[1] == 0x37) then --FAN BOOST ON
  539. C4:UpdateProperty("Current Fanspeed", "BOOST")
  540. C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "HIGH"})
  541. C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "High"})
  542. Dbg("FAN BOOST = ON")
  543. elseif(packet[1] == 0x38) then --FAN BOOST OFF
  544. C4:UpdateProperty("Current Fanspeed", "NORMAL")
  545. C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "AUTO"})
  546. C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "Auto"})
  547. Dbg("FAN BOOST = OFF")
  548. elseif(packet[1] == 0x56) then --FLAME EFFECT ON
  549. C4:UpdateProperty("Flame Effect", "ON")
  550. C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
  551. Dbg("FLAME EFFECT = ON")
  552. currentState = "close"
  553. elseif(packet[1] == 0x55) then --FLAME EFFECT OFF
  554. C4:UpdateProperty("Flame Effect", "OFF")
  555. C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
  556. Dbg("FLAME EFFECT = OFF")
  557. currentState = "open"
  558. elseif(packet[1] == 0x57) then --NEW TEMP
  559. C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tempSetpoint, SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
  560. end
  561. end
  562. end
  563. UDPServer:close()
  564. Dbg("UDP Server Closed")
  565. if (UDPPollTimer) then UDPPollTimer = C4:KillTimer(UDPPollTimer) end
  566. else
  567. print("There is no active licence or trial licence. This driver has been disabled")
  568. end
  569. end
  570.  
  571. function ProcessFind(packet)
  572. local pin = Hex2Dec(string.format("%x",packet[3]) .. string.format("%x",packet[4]) .. string.format("%x",packet[5]) .. string.format("%x",packet[6]))
  573. local serial = Hex2Dec(string.format("%x",packet[7]) .. string.format("%x",packet[8]))
  574. Dbg("PIN = " .. pin)
  575. Dbg("SERIAL = " .. serial)
  576. end
  577.  
  578. function ProcessStatus(packet)
  579. if(packet[3] == 0x01) then --Fireplace has new timers
  580. else
  581. end
  582. if(packet[4] == 0x01) then--Fireplace is on
  583. C4:UpdateProperty("Current Mode", "ON")
  584. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Heat"})
  585. C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Heat"})
  586. Dbg("POWER = ON")
  587. else
  588. C4:UpdateProperty("Current Mode", "OFF")
  589. C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Off"})
  590. C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Off"})
  591. Dbg("POWER = OFF")
  592. end
  593. if(packet[5] == 0x01) then --Fan boost is on
  594. C4:UpdateProperty("Current Fanspeed", "BOOST")
  595. C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "HIGH"})
  596. C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "HIGH"})
  597. Dbg("FAN BOOST = ON")
  598. else
  599. C4:UpdateProperty("Current Fanspeed", "NORMAL")
  600. C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "AUTO"})
  601. C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "AUTO"})
  602. Dbg("FAN BOOST = OFF")
  603. end
  604. if(packet[6] == 0x01) then --Flame effect is on
  605. C4:UpdateProperty("Flame Effect", "ON")
  606. C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
  607. currentState = "close"
  608. Dbg("FLAME EFFECT = ON")
  609. else
  610. C4:UpdateProperty("Flame Effect", "OFF")
  611. C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
  612. Dbg("FLAME EFFECT = OFF")
  613. currentState = "open"
  614. end
  615. local setPoint = Hex2Dec(string.format("%x",packet[7])) --Set point (degrees c)
  616. C4:UpdateProperty("Current Setpoint", setPoint .. " DEGREES")
  617. C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tonumber(setPoint), SCALE = "F"})
  618. local roomTemp = Hex2Dec(string.format("%x",packet[8])) --Room temp (degrees c)
  619. C4:UpdateProperty("Current Temperature", roomTemp .. " DEGREES")
  620. C4:SendToProxy(5001, "TEMPERATURE_CHANGED", {TEMPERATURE = tonumber(roomTemp), SCALE = "F"})
  621. Dbg("SETPOINT = " .. setPoint)
  622. Dbg("ROOMTEMP = " .. roomTemp)
  623. end
  624.  
  625. function StringTokenise(str, seperator, skipEmpty)
  626. local start = 1
  627. local pos = 1
  628. local tokens = {"", ""}
  629. local i = 1
  630. str = string.gsub(str, " ", "")
  631. while (str ~= nil) do
  632. pos = string.find(str, seperator, start)
  633.  
  634. if (pos ~= nil) then
  635. if (skipEmpty ~= true) or
  636. (pos > start + 1) then
  637. tokens[i] = string.sub(str, start, pos - 1)
  638. i = i + 1
  639. end
  640.  
  641. start = pos + 1
  642. else
  643. tokens[i] = string.sub(str, start)
  644. break
  645. end
  646. end
  647.  
  648. return tokens
  649. end
  650.  
  651. function returnByte(Byte)
  652. if string.len(string.format("%x",Byte, 1)) < 2 then
  653. return ("0" .. string.format("%x",Byte, 1))
  654. else
  655. return string.format("%x",Byte, 1)
  656. end
  657. end
  658. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  659. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< DEBUG FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  660. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  661.  
  662. debugFileName = "chowmain_escea_fireplace.log" --whole filename case sensitive
  663.  
  664. -------------------------------------------------------------------
  665. --Function Name : Dbg(debugString)
  666. --Parameters : debugString(string)
  667. --Description : Function called to output debug
  668. -------------------------------------------------------------------
  669.  
  670. function Dbg(debugString)
  671. local dbgStr = os.date() .. " - " .. debugString .. "\r\n\r\n"
  672. if (debugMode == 1) then print(dbgStr)
  673. elseif (debugMode == 2) then Dbglog(dbgStr)
  674. elseif (debugMode == 3) then
  675. print(dbgStr)
  676. Dbglog(dbgStr)
  677. end
  678. end
  679. -------------------------------------------------------------------
  680. --Function Name : Dbglog(debugString)
  681. --Parameters : debugString(string)
  682. --Description : Function called to output debug to file
  683. -------------------------------------------------------------------
  684.  
  685. function Dbglog(debugString, ...)
  686. C4:FileSetDir("/var/log/") -- Set file path (new one now)
  687. fh = C4:FileOpen(debugFileName) -- Attempt to open file
  688. if (fh == -1) then print("ERROR: Debug failed log to file\r\n")
  689. else
  690. local fileSize = C4:FileGetSize(fh)
  691. if (tonumber(fileSize) > 2097152) then --More then 2MB in size. LogRotate failed. Delete file.
  692. print("LOG FILESIZE IS OVER 2MB. LOGROTATE FAILURE. DELETING FILE")
  693. C4:FileClose(fh)
  694. if (C4:FileDelete(debugFileName)) then
  695. print("LOG DELETION SUCCESSFUL")
  696. fileSize = 0
  697. else
  698. print("LOGF DELETION FAILURE")
  699. end
  700. fh = C4:FileOpen(debugFileName) -- Attempt to open file
  701. end
  702. C4:FileSetPos(fh, fileSize) -- Start writing from end of file
  703. bytesWritten = C4:FileWrite(fh, string.len(debugString), debugString)
  704. C4:FileClose(fh)
  705. end
  706. end
  707.  
  708. -------------------------------------------------------------------
  709. --Function Name : DbgPad(strToPad, length)
  710. --Parameters : strToPad(string), length(int)
  711. --Description : Function called to pad 0's in front of string
  712. -------------------------------------------------------------------
  713.  
  714.  
  715. function DbgPad(strToPad, length)
  716. if (string.len(strToPad) >= length) then
  717. return strToPad
  718. else
  719. for i = string.len(strToPad), length -1, 1 do
  720. strToPad = "0" .. strToPad
  721. end
  722. return strToPad
  723. end
  724. end
  725.  
  726. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  727. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LICENCE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>
  728. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  729.  
  730. MASTER_NUMBER = 308
  731. PRODUCT_NUMBER = 132
  732. SOFTWARE_VERSION = "8.4.1"
  733. TrialPeriodHours = 48
  734.  
  735. GeoLocationEnabled = false
  736. GeoLocationIP = ""
  737. GeoLocationCountryCode = ""
  738. GeoLocationCountryName = ""
  739. GeoLocationCountryRegionCode = ""
  740. GeoLocationCountryRegionName = ""
  741. GeoLocationCountryCity = ""
  742. GeoLocationCountryZipCode = ""
  743. GeoLocationCountryLatitude = ""
  744. GeoLocationCountryLongitude = ""
  745.  
  746. AllowedCountryCode = {'NZ','AU'}
  747. DisallowedCountryCode = {'NZ','AU'}
  748. AllowedCities = {}
  749. C4:UpdateProperty("Control4 MAC Address", C4:GetUniqueMAC())
  750.  
  751. -------------------------------------------------------------------
  752. --Function Name : GetFormattedDate()
  753. --Parameters :
  754. --Description : function to return date in format MM/DD/YYYY HH:MM:
  755. -------------------------------------------------------------------
  756.  
  757. function GetFormattedDate()
  758. return(os.date('*t').month .. "/" .. os.date('*t').day .. "/" .. os.date('*t').year .. " " .. os.date('*t').hour .. ":" .. string.format("%02d",tonumber(os.date('*t').min )).. ": ")
  759. end
  760.  
  761. -------------------------------------------------------------------
  762. --Function Name : DoesFileExist(filename,type)
  763. --Parameters : filename(string),type(c4z or c4i)
  764. --Description : Function to check if location is valid
  765. -------------------------------------------------------------------
  766.  
  767. function Licence_HouselogixActivate()
  768. --if(string.len(Properties["Activation Key"]) > 0) then
  769. C4:UpdateProperty("Activation Status", "Checking key, please wait")
  770. local postData = string.format("lic=%s&mac=%s&p=%s&ver=%s", Properties["Activation Key"], C4:GetUniqueMAC(), PRODUCT_NUMBER, SOFTWARE_VERSION)
  771. houselogixLicenceCheck = C4:urlPost("https://w...content-available-to-author-only...x.com/license-manager/v1/", postData)
  772. --end
  773. end
  774.  
  775. -------------------------------------------------------------------
  776. --Function Name : IsGeoLocationValid()
  777. --Parameters :
  778. --Description : Function to check if location is valid
  779. -------------------------------------------------------------------
  780.  
  781. function Licence_IsGeoLocationValid()
  782. if (GeoLocationEnabled == false) then return true end
  783. for _,country in pairs(DisallowedCountryCode) do
  784. if (GeoLocationCountryCode == country) then return false end
  785. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - USAGE OUTSIDE OF VALID REGION")
  786. end
  787. for _,country in pairs(AllowedCountryCode) do
  788. if (GeoLocationCountryCode == country) then return true end
  789. end
  790. for _,city in pairs(AllowedCities) do
  791. if (GeoLocationCountryCity == city) then return true end
  792. end
  793. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - USAGE OUTSIDE OF VALID REGION")
  794. return false
  795. end
  796.  
  797. -------------------------------------------------------------------
  798. --Function Name : GetGeoLocation()
  799. --Parameters :
  800. --Description : Function to get location
  801. -------------------------------------------------------------------
  802.  
  803. function Licence_GetGeoLocation()
  804. geoLocation = C4:urlGet("http://f...content-available-to-author-only...p.net/xml/")
  805. end
  806.  
  807. function Licence_Is_Valid()
  808. if(PersistData["LicenceData"] == nil) then
  809. if(Licence_IsGeoLocationValid()) then
  810. --Licence_Trial()
  811. return true
  812. else
  813. return false
  814. end
  815. elseif(PersistData["LicenceData"]["State"] == "TRIAL") then
  816. if(Licence_IsGeoLocationValid()) then
  817. return true
  818. else
  819. return false
  820. end
  821. elseif(PersistData["LicenceData"]["State"] == "VALID") then
  822. if(Licence_IsGeoLocationValid()) then
  823. return true
  824. else
  825. return false
  826. end
  827. elseif(PersistData["LicenceData"]["State"] == "INVALID") then
  828. return false
  829. end
  830. end
  831.  
  832.  
  833.  
  834. function Licence_Trial_Hours_Remaining()
  835. return TrialPeriodHours - math.floor((os.time() - PersistData["LicenceData"]["Trial Started"]) / 3600)
  836. end
  837.  
  838. function Licence_CreateTrial()
  839. if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {} end
  840. if (nil == PersistData["LicenceData"]["State"]) then --new trial licence
  841. PersistData["LicenceData"]["State"] = "TRIAL"
  842. PersistData["LicenceData"]["Trial Started"] = os.time()
  843. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
  844. if(trialLicenceTimer == nil or trialLicenceTimer == 0) then trialLicenceTimer = C4:AddTimer("1","HOURS",true) end
  845. elseif((PersistData["LicenceData"]["State"] == "VALID") and (string.len(Properties["Activation Key"]) > 0)) then --Don't do anything the state is valid
  846. if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  847. else
  848. if(Licence_Trial_Hours_Remaining() < 1) then
  849. PersistData["LicenceData"]["State"] = "INVALID"
  850. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL EXPIRED AND INVALID KEY")
  851. if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  852. else
  853. PersistData["LicenceData"]["State"] = "TRIAL"
  854. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
  855. if(trialLicenceTimer == nil or trialLicenceTimer == 0) then trialLicenceTimer = C4:AddTimer("1","HOURS",true) end
  856. end
  857. end
  858. end
  859.  
  860. function Licence_ProcessTrialTimer()
  861. if(PersistData["LicenceData"]["State"] ~= "VALID") then
  862. if(Licence_Trial_Hours_Remaining() < 1) then
  863. PersistData["LicenceData"]["State"] = "INVALID"
  864. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL EXPIRED AND INVALID KEY")
  865. if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  866. else
  867. PersistData["LicenceData"]["State"] = "TRIAL"
  868. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
  869. end
  870. else
  871. if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  872. end
  873. end
  874.  
  875. -------------------------------------------------------------------
  876. --Function Name : ReceivedAsync(ticketId, strData, responseCode, tHeaders)
  877. --Parameters : ticketId(number), strData(string), responseCode(string), tHeaders(table)
  878. --Description : Function called by Control4 when url get command returned
  879. -------------------------------------------------------------------
  880.  
  881. function ReceivedAsync(ticketId, strData, responseCode, tHeaders)
  882.  
  883. --LICENCING CODE GOES UNDER HERE
  884. if(ticketId == masterLicenceCheck) then
  885. -- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {['State'] = nil} end
  886. -- if(string.find(strData,"Failed to get")) then --houselogix is down.
  887. -- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - ACTIVATION SERVER NOT RESPONDING")
  888. -- elseif string.find(strData,"Valid") then
  889. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "ACTIVATED")
  890. PersistData["LicenceData"]["State"] = "VALID"
  891. -- if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  892. -- elseif string.find(strData,"Unauthorized") then
  893. -- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - INVALID KEY")
  894. -- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {} end
  895. -- PersistData["LicenceData"]["State"] = "INVALID"
  896. -- Licence_ProcessTrialTimer()
  897. -- else
  898. -- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - RECEIVED UNKNOWN RESPONSE")
  899. -- end
  900. return
  901. end
  902. if(ticketId == houselogixLicenceCheck) then
  903. -- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {['State'] = nil} end
  904. -- if(string.find(strData,"Failed to get")) then --houselogix is down.
  905. -- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - ACTIVATION SERVER NOT RESPONDING")
  906. -- elseif string.find(strData,"Valid") then
  907. C4:UpdateProperty("Activation Status", GetFormattedDate() .. "ACTIVATED")
  908. PersistData["LicenceData"]["State"] = "VALID"
  909. -- if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  910. -- else
  911. -- local postData = string.format("lic=%s&mac=%s&p=%s&ver=%s", Properties["Activation Key"], C4:GetUniqueMAC(), MASTER_NUMBER, SOFTWARE_VERSION)
  912. -- masterLicenceCheck = C4:urlPost("https://w...content-available-to-author-only...x.com/license-manager/v1/", postData)
  913. -- end
  914. return
  915. end
  916. if(ticketId == geoLocation) then
  917. if(string.find(strData,"Failed to get")) then --freegeoip.net is down. Enable the driver
  918. GeoLocationEnabled = false
  919. Dbg("Cannot get Geo Location. Enabling Driver for all locations")
  920. else
  921. local XMLdata = C4:ParseXml(strData)
  922. if(XMLdata.Name == "Response") then --Response
  923. for _, NodeValue in pairs(XMLdata.ChildNodes) do
  924. if(NodeValue.Name == "IP") then GeoLocationIP = NodeValue.Value end
  925. if(NodeValue.Name == "CountryCode") then GeoLocationCountryCode = NodeValue.Value end
  926. if(NodeValue.Name == "CountryName") then GeoLocationCountryName = NodeValue.Value end
  927. if(NodeValue.Name == "RegionCode") then GeoLocationCountryRegionCode = NodeValue.Value end
  928. if(NodeValue.Name == "RegionName") then GeoLocationCountryRegionName = NodeValue.Value end
  929. if(NodeValue.Name == "City") then GeoLocationCountryCity = NodeValue.Value end
  930. if(NodeValue.Name == "ZipCode") then GeoLocationCountryZipCode = NodeValue.Value end
  931. if(NodeValue.Name == "Latitude") then GeoLocationCountryLatitude = NodeValue.Value end
  932. if(NodeValue.Name == "Longitude") then GeoLocationCountryLongitude = NodeValue.Value end
  933. end
  934. end
  935. end
  936. end
  937. end
  938.  
  939.  
  940. -------------------------------------------------------------------
  941. --Function Name : OnDriverDestroyed()
  942. --Parameters :
  943. --Description : Function called when driver deleted or updated
  944. -------------------------------------------------------------------
  945.  
  946. function OnDriverDestroyed()
  947.  
  948. --LICENCING CODE GOES UNDER HERE
  949. --Clean up Control4 variables. This is required for EV auto update functionality
  950.  
  951. --Kill all timers. This is required for EV auto update functionality
  952. if(houselogixLicence) then houselogixLicence = C4:KillTimer(houselogixLicence) end
  953. if(trialLicenceTimer) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
  954. end
  955.  
  956. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  957. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /LICENCE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>
  958. --<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  959.  
  960. --//////////--//////////--//////////--//////////--//////////--//////////--//////////
  961. --//////////--//////////--//////////--//////////--//////////--//////////--//////////
  962. -- EXTRAS SETUP
  963. --//////////--//////////--//////////--//////////--//////////--//////////--//////////
  964. --//////////--//////////--//////////--//////////--//////////--//////////--//////////
  965. function removeNewlines(string)
  966. return string.gsub(string, [[\n]], "") -- remove line breaks
  967. end
  968.  
  969. function NotifyProxy(Notification, tParams)
  970. C4:SendToProxy(5001, Notification, tParams, "NOTIFY") -- , "NOTIFY"
  971. end
  972.  
  973. -- Add "Flame Level" to the proxy's "Extras" tab
  974. function SendExtrasSetupToProxy()
  975. Dbg("SendExtrasSetupToProxy")
  976. xml = [[
  977. <extras_setup>
  978. <extra>
  979.  
  980. <object type="list" id="FlameLevel" label="Flame Level" command="SET_FLAME_LEVEL" param_name="FlameLevel">
  981. <list>
  982. <item text="]] .. 'Level 1' .. [[" value="0"/>
  983. <item text="]] .. 'Level 2' .. [[" value="1"/>
  984. <item text="]] .. 'Level 3' .. [[" value="2"/>
  985. <item text="]] .. 'Level 4' .. [[" value="3"/>
  986. <item text="]] .. 'Level 5' .. [[" value="4"/>
  987. </list>
  988. </object>
  989.  
  990. </extra>
  991. </extras_setup>
  992. ]]
  993. NotifyProxy("EXTRAS_SETUP_CHANGED", { XML = removeNewlines(xml) })
  994. --for k,v in pairs(C4:GetDeviceVariables(928)) do print(k, v.name, v.value) end
  995. print(C4:GetDeviceVariable(928,1130))
  996. end
  997.  
  998. -- Change Extras State
  999. function SendExtrasStateToProxy(value)
  1000. xml = [[
  1001. <extras_state>
  1002. <extra>
  1003. ]]
  1004.  
  1005. xml = xml .. [[<object id="FlameLevel" hidden="false" value="]] .. value .. [["/>]]
  1006.  
  1007. xml = xml .. [[
  1008. </extra>
  1009. </extras_state>
  1010. ]]
  1011. NotifyProxy("EXTRAS_STATE_CHANGED", { XML = removeNewlines(xml) })
  1012. end
  1013.  
  1014. -- Update the Flame Level Extra when the Flame Level property changes
  1015. function PROPS.FlameLevel(value)
  1016. dbg('(PROPS.FlameLevel): ', value)
  1017. if value == '1' then
  1018. SendExtrasStateToProxy(0)
  1019. elseif value == '2' then
  1020. SendExtrasStateToProxy(1)
  1021. end
  1022. end
  1023.  
  1024. function removeNewlines(string)
  1025. return string.gsub(string, [[\n]], "") -- remove line breaks
  1026. end
  1027.  
  1028. for k,v in pairs(Properties) do
  1029. OnPropertyChanged(k)
  1030. end
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
luac5.3: prog.lua:1: unexpected symbol near '<'
stdout
Standard output is empty