<![CDATA[----------------------------------------------------------------------
-- VARIABLES
----------------------------------------------------------------------
FireplaceCommands = {
['STATUS'] = "473100000000000000000000003146",
['ON'] = "473900000000000000000000003946",
['OFF'] = "473A00000000000000000000003A46",
['FIND'] = "475000000000000000000000005046",
['FAN_BOOST_ON'] = "473700000000000000000000003746",
['FAN_BOOST_OFF'] = "473800000000000000000000003846",
['FLAME_EFFECT_ON'] = "475600000000000000000000005646",
['FLAME_EFFECT_OFF'] = "475500000000000000000000005546"
}
TemperatureCommands = {
[3] = "475701030000000000000000005B46",
[4] = "475701040000000000000000005C46",
[5] = "475701050000000000000000005D46",
[6] = "475701060000000000000000005E46",
[7] = "475701070000000000000000005F46",
[8] = "475701080000000000000000006046",
[9] = "475701090000000000000000006146",
[10] = "4757010A0000000000000000006246",
[11] = "4757010B0000000000000000006346",
[12] = "4757010C0000000000000000006446",
[13] = "4757010D0000000000000000006546",
[14] = "4757010E0000000000000000006646",
[15] = "4757010F0000000000000000006746",
[16] = "475701100000000000000000006846",
[17] = "475701110000000000000000006946",
[18] = "475701120000000000000000006A46",
[19] = "475701130000000000000000006B46",
[20] = "475701140000000000000000006C46",
[21] = "475701150000000000000000006D46",
[22] = "475701160000000000000000006E46",
[23] = "475701170000000000000000006F46",
[24] = "475701180000000000000000007046",
[25] = "475701190000000000000000007146",
[26] = "4757011A0000000000000000007246",
[27] = "4757011B0000000000000000007346",
[28] = "4757011C0000000000000000007446",
[29] = "4757011D0000000000000000007546",
[30] = "4757011E0000000000000000007646",
[31] = "4757011F0000000000000000007746"
}
FireplaceFeedback = {
['STATUS'] = "0x80",
['ON'] = "0x8D",
['OFF'] = "0x8F",
['FLAME_EFFECT_ON'] = "0x61",
['FLAME_EFFECT_OFF'] = "0x60",
['NEW_SET_TEMP_ACK'] = "0x66",
['I_AM_A_FIRE'] = "0x90"
}
currentState = "open"
tempSetpoint = 0
g_deviceDataPollTimer = 0
-- These functions are big-endian and take up to 32 bits
hex2bin = {
["0"] = "0000",
["1"] = "0001",
["2"] = "0010",
["3"] = "0011",
["4"] = "0100",
["5"] = "0101",
["6"] = "0110",
["7"] = "0111",
["8"] = "1000",
["9"] = "1001",
["a"] = "1010",
["b"] = "1011",
["c"] = "1100",
["d"] = "1101",
["e"] = "1110",
["f"] = "1111"
}
bin2hex = {
["0000"] = "0",
["0001"] = "1",
["0010"] = "2",
["0011"] = "3",
["0100"] = "4",
["0101"] = "5",
["0110"] = "6",
["0111"] = "7",
["1000"] = "8",
["1001"] = "9",
["1010"] = "A",
["1011"] = "B",
["1100"] = "C",
["1101"] = "D",
["1110"] = "E",
["1111"] = "F"
}
function OnTimerExpired(idTimer)
local timer = pullTimer(idTimer)
if (not timer) then dbg("UNKNOWN TIMER FIRED | THROWING AWAY") return end
timer.handler()
end
--------------------------------
-- Helpers
--------------------------------
g_timers = {}
function startTimer(value, units, callback)
local timerId = C4:AddTimer(tonumber(value), units)
g_timers[timerId] = { handler = callback }
return timerId
end
function pullTimer(timerId)
local timer = g_timers[timerId]
g_timers[timerId] = nil
return timer
end
function killTimer(timerId)
local timer = g_timers[timerId]
g_timers[timerId] = nil
if tonumber(timerId) > 0 then
C4:KillTimer(timerId)
end
return timer
end
-- Hex2Bin
-- Bin2Hex
-- Hex2Dec
-- Dec2Hex
-- Bin2Dec
-- Dec2Bin
function Hex2Bin(s) -- s -> hexadecimal string
local ret = ""
local i = 0
for i in string.gfind(s, ".") do
i = string.lower(i)
ret = ret..hex2bin[i]
end
return ret
end
function Bin2Hex(s) -- s -> binary string
local l = 0
local h = ""
local b = ""
local rem
l = string.len(s)
rem = l % 4
l = l-1
h = ""
-- need to prepend zeros to eliminate mod 4
if (rem > 0) then
s = string.rep("0", 4 - rem)..s
end
for i = 1, l, 4 do
b = string.sub(s, i, i+3)
h = h..bin2hex[b]
end
return h
end
function Bin2Dec(s)
-- s -> binary string
local num = 0
local ex = string.len(s) - 1
local l = 0
l = ex + 1
for i = 1, l do
b = string.sub(s, i, i)
if b == "1" then
num = num + 2^ex
end
ex = ex - 1
end
return string.format("%u", num)
end
function Dec2Bin(s, num) -- s -> Base10 string
-- num -> string length to extend to
local n
if (num == nil) then
n = 0
else
n = num
end
s = string.format("%x", s)
s = Hex2Bin(s)
while string.len(s) < n do
s = "0"..s
end
return s
end
function Hex2Dec(s)
-- s -> hexadecimal string
local s = Hex2Bin(s)
return Bin2Dec(s)
end
function Dec2Hex(s)
-- s -> Base10 string
s = string.format("%x", s)
return s
end
function GenerateChecksum(data, offset, count)
local polynomial = 0x8408
local crc = 0xFFFF
if (count == 0) then
return 0
else
for i = 0, count, 1 do
local current = data[offset + i]
for j = 0, 7 do
if (bit.band(bit.bxor(crc, current),0x1) > 0) then
crc = bit.bxor(bit.rshift(crc,1),polynomial) % 65536
else
crc = bit.rshift(crc,1)
end
current = bit.rshift(current,1)
end
end
return bit.bnot(crc) % 65536
end
end
-------------------------------------------------------------------
--Function Name : OnPropertyChanged(strName)
--Parameters : idTimer(int)
--Description : Function called when property is changed
-------------------------------------------------------------------
function OnPropertyChanged(strProperty)
if (strProperty == "Debug Mode") then
if (Properties[strProperty] == "Off") then
print("DEBUG MODE = OFF\r\n")
debugMode = 0
elseif (Properties[strProperty] == "Print") then
print("DEBUG MODE = PRINT\r\n")
debugMode = 1
debugTimer = C4:AddTimer(168,"HOURS",false)
elseif (Properties[strProperty] == "Log") then
print("DEBUG MODE = LOG\r\n")
debugMode = 2
debugTimer = C4:AddTimer(168,"HOURS",false)
elseif (Properties[strProperty] == "Print and Log") then
print("DEBUG MODE = PRINT AND LOG\r\n")
debugMode = 3
debugTimer = C4:AddTimer(168,"HOURS",false)
end
end
if (strProperty == "IP Address") then
OnDriverLateInit()
end
--LICENCING CODE GOES UNDER HERE
if(strProperty == "Activation Key") then
-- Licence_CreateTrial()
if(string.len(Properties[strProperty]) > 0) then
Licence_HouselogixActivate()
end
elseif(strProperty == "Control4 MAC Address") then
if(string.lower(Properties[strProperty]) == "uuddlrlrba") then
C4:AllowExecute(true)
C4:UpdateProperty("Driver Information", "Konami Code Detected - Lua Command Window Activated")
else
C4:AllowExecute(false)
C4:UpdateProperty("Driver Information", "")
C4:UpdateProperty("Control4 MAC Address", C4:GetUniqueMAC())
end
end
end
-------------------------------------------------------------------
--Function Name : ReceivedFromProxy
--Parameters : idBinding(int), strCommand (string), tParams(table)
--Description : Function called when proxy command is called
-------------------------------------------------------------------
function ReceivedFromProxy(idBinding, strCommand, tParams)
if(Licence_Is_Valid()) then
Dbg("ReceivedFromProxy [" .. idBinding .. "] : " .. strCommand)
if (tParams ~= nil) then
for ParamName, ParamValue in pairs(tParams) do
Dbg(ParamName .. " " .. ParamValue)
end
end
if(strCommand == "TOGGLE" or strCommand == "CLICK_TOGGLE_BUTTON" or strCommand == "TOGGLE_PRESET") then
if(currentState == "close") then
SendMessage(FireplaceCommands["FLAME_EFFECT_OFF"])
currentState = "open"
else
SendMessage(FireplaceCommands["FLAME_EFFECT_ON"])
currentState = "close"
end
end
if(strCommand == "OPEN") then
SendMessage(FireplaceCommands["FLAME_EFFECT_OFF"])
C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
currentState = "open"
end
if(strCommand == "CLOSE") then
SendMessage(FireplaceCommands["FLAME_EFFECT_ON"])
C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
currentState = "close"
end
if (strCommand == "SET_SCALE") then
C4:SendToProxy(5001, "SCALE_CHANGED", {SCALE = "FAHRENHEIT"}) --only works with fahrenheit
C4:UpdateProperty("Current Scale", "FAHRENHEIT")
end
if (strCommand == "SET_MODE_FAN") then
if(tParams["MODE"] == "AUTO" or tParams["MODE"] == "Auto") then
SendMessage(FireplaceCommands['FAN_BOOST_OFF'])
else
SendMessage(FireplaceCommands['FAN_BOOST_ON'])
end
end
if (strCommand == "SET_MODE_HVAC") then
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", tParams)
C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = tParams["MODE"]})
if(tParams["MODE"] == "OFF" or tParams["MODE"] == "Off") then
SendMessage(FireplaceCommands["OFF"])
else
SendMessage(FireplaceCommands["ON"])
end
elseif (strCommand == "SET_SETPOINT_HEAT") then
C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tParams["SETPOINT"], SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
SendMessage(TemperatureCommands[tonumber(tParams["SETPOINT"])])
tempSetpoint = tonumber(tParams["SETPOINT"])
elseif (strCommand == "SET_POINT") then
if(tonumber(setpoint) > 0) then
SendMessage(TemperatureCommands[tonumber(tParams["SETPOINT"])])
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = mode})
C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tParams["SETPOINT"], SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
else
SendMessage(FireplaceCommands["OFF"])
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = mode})
end
tempSetpoint = tonumber(tParams["SETPOINT"])
end
else
print("There is no active licence or trial licence. This driver has been disabled")
end
end
function StatusPlease()
SendMessage(FireplaceCommands["STATUS"])
end
function SendMessage(TX_MESSAGE)
createUDPServer()
message = tohex(TX_MESSAGE)
C4:CreateNetworkConnection (6001, Properties['IP Address'])
C4:NetConnect(6001, 3300, "UDP")
end
function OnDriverUpdate()
OnDriverLateInit()
end
function OnDriverUpdated()
OnDriverLateInit()
end
function PollData()
Dbg("PollData")
local curTemp = C4:GetDeviceVariable(928,1130)
C4:UpdateProperty("Current Temperature", curTemp .. " DEGREES")
C4:SendToProxy(5001, "TEMPERATURE_CHANGED", {TEMPERATURE = tonumber(curTemp), SCALE = "F"})
killTimer(g_deviceDataPollTimer)
g_deviceDataPollTimer = startTimer(15, "SECONDS",
function()
PollData()
end)
end
-------------------------------------------------------------------
--Function Name : OnDriverLateInit()
--Parameters :
--Description : Function called after properties has initialised
-------------------------------------------------------------------
function OnDriverLateInit()
--if (recreateServer ~= nil) then recreateServer = C4:KillTimer(recreateServer) end
--recreateServer = C4:AddTimer(10, "MINUTES", true)
if (status_Timer ~= nil) then status_Timer = C4:KillTimer(status_Timer) end
status_Timer = C4:AddTimer(math.random(30,90), "SECONDS", true)
createUDPServerTimer = C4:AddTimer(60, "SECONDS", false)
C4:SendToProxy(5001, "SCALE_CHANGED", {SCALE = "FAHRENHEIT"})
g_deviceDataPollTimer = startTimer(60, "SECONDS", function() PollData() end)
Dbg("OnDriverLateInit")
--LICENCING CODE GOES UNDER HERE
if((houselogixLicence == nil) or (houselogixLicence == 0)) then houselogixLicence = C4:AddTimer("24","HOURS",true) end
if((startupTimer == nil) or (startupTimer == 0)) then startupTimer = C4:AddTimer("2","MINUTES",false) end
-- Licence_CreateTrial()
C4:UpdateProperty("Driver Version", SOFTWARE_VERSION)
startTimer(10, 'SECONDS', function()
-- Extras
SendExtrasSetupToProxy()
SendExtrasStateToProxy(0)
end)
end
function createUDPServer()
UDPServer = socket.udp()
UDPServer:setsockname("*", 3300)
UDPServer:settimeout(0)
Dbg("Escea: UDP Server Created\r\n")
--if (UDPPollTimer) then UDPPollTimer = C4:KillTimer(UDPPollTimer) end
UDPPollTimer = C4:AddTimer(3, "SECONDS", false)
Dbg("UDP Server:Timer Created\r\n")
end
-------------------------------------------------------------------
--Function Name : OnTimerExpired()
--Parameters : idTimer (int)
--Description : Function to check expired timers
-------------------------------------------------------------------
function OnTimerExpired(idTimer)
if (idTimer == createUDPServerTimer) then
--createUDPServer()
end
if (idTimer == UDPPollTimer) then
OnReceivedData()
end
if idTimer == recreateServer then
--UDPServer:close()
--createUDPServer()
end
if (idTimer == status_Timer) then --Poll the fireplace for status feedback
StatusPlease()
end
if (idTimer == debugTimer) then
Dbg("Debug Logs left on for a week. Turning Off")
C4:UpdateProperty("Debug Mode", "Off")
end
--LICENCING CODE GOES UNDER HERE
if(idTimer == trialLicenceTimer) then
Licence_ProcessTrialTimer()
elseif(idTimer == startupTimer) then
Licence_HouselogixActivate()
elseif(idTimer == houselogixLicence) then
Licence_HouselogixActivate()
end
end
-------------------------------------------------------------------
--Function Name : OnConnectionStatusChanged(idBinding, nPort, strStatus)
--Parameters : idBinding(int), nPort(int), strStatus(string)
--Description : Function called when socket connect connected/disconnected
-------------------------------------------------------------------
function OnConnectionStatusChanged(idBinding, nPort, strStatus)
Dbg("idBinding = " .. idBinding .. "\r\nnPort = " .. nPort .. "\r\nstrStatus = " .. strStatus)
if (strStatus == "ONLINE") then
C4:SendToNetwork(6001, 3300, message)
Dbg("Sending message: ")
hexdump(message)
end
end
-------------------------------------------------------------------
--Function Name : ReceivedFromNetwork
--Parameters : idBinding(int), nPort(int), strData(string)
--Description : Function called when data received on socket connection
-------------------------------------------------------------------
function OnReceivedData()
local Data,ip = UDPServer:receivefrom()
if string.find(ip,Properties['IP Address']) then
elseif(string.find(ip,"refused")) then
else
Dbg("Data received from different IP Address: " .. ip)
return
end
if(Licence_Is_Valid()) then
if Data ~= nil then
Dbg("Receiving message (" .. ip .. "):")
hexdump(Data)
local packet = {}
for i = 0, 14 do
packet[i] = tonumber(Hex2Dec(returnByte(string.byte(Data,i+1))))
end
if((packet[0] == 0x47) and (packet[14] == 0x46)) then
Dbg("Received message")
if(packet[1] == 0x80) then --STATUS
ProcessStatus(packet)
elseif(packet[1] == 0x90) then --I AM A FIRE
ProcessFind(packet)
elseif(packet[1] == 0x39) then --POWER ON
C4:UpdateProperty("Current Mode", "ON")
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Heat"})
C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Heat"})
Dbg("POWER = ON")
elseif(packet[1] == 0x3A) then --POWER OFF
C4:UpdateProperty("Current Mode", "OFF")
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Off"})
C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Off"})
Dbg("POWER = OFF")
elseif(packet[1] == 0x37) then --FAN BOOST ON
C4:UpdateProperty("Current Fanspeed", "BOOST")
C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "HIGH"})
C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "High"})
Dbg("FAN BOOST = ON")
elseif(packet[1] == 0x38) then --FAN BOOST OFF
C4:UpdateProperty("Current Fanspeed", "NORMAL")
C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "AUTO"})
C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "Auto"})
Dbg("FAN BOOST = OFF")
elseif(packet[1] == 0x56) then --FLAME EFFECT ON
C4:UpdateProperty("Flame Effect", "ON")
C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
Dbg("FLAME EFFECT = ON")
currentState = "close"
elseif(packet[1] == 0x55) then --FLAME EFFECT OFF
C4:UpdateProperty("Flame Effect", "OFF")
C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
Dbg("FLAME EFFECT = OFF")
currentState = "open"
elseif(packet[1] == 0x57) then --NEW TEMP
C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tempSetpoint, SCALE = string.sub(C4:GetDeviceVariable(C4:GetProxyDevices(),1100),1,1)})
end
end
end
UDPServer:close()
Dbg("UDP Server Closed")
if (UDPPollTimer) then UDPPollTimer = C4:KillTimer(UDPPollTimer) end
else
print("There is no active licence or trial licence. This driver has been disabled")
end
end
function ProcessFind(packet)
local pin = Hex2Dec(string.format("%x",packet[3]) .. string.format("%x",packet[4]) .. string.format("%x",packet[5]) .. string.format("%x",packet[6]))
local serial = Hex2Dec(string.format("%x",packet[7]) .. string.format("%x",packet[8]))
Dbg("PIN = " .. pin)
Dbg("SERIAL = " .. serial)
end
function ProcessStatus(packet)
if(packet[3] == 0x01) then --Fireplace has new timers
else
end
if(packet[4] == 0x01) then--Fireplace is on
C4:UpdateProperty("Current Mode", "ON")
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Heat"})
C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Heat"})
Dbg("POWER = ON")
else
C4:UpdateProperty("Current Mode", "OFF")
C4:SendToProxy(5001, "HVAC_MODE_CHANGED", {MODE = "Off"})
C4:SendToProxy(5001, "HVAC_STATE_CHANGED", {STATE = "Off"})
Dbg("POWER = OFF")
end
if(packet[5] == 0x01) then --Fan boost is on
C4:UpdateProperty("Current Fanspeed", "BOOST")
C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "HIGH"})
C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "HIGH"})
Dbg("FAN BOOST = ON")
else
C4:UpdateProperty("Current Fanspeed", "NORMAL")
C4:SendToProxy(5001, "FAN_MODE_CHANGED", {MODE = "AUTO"})
C4:SendToProxy(5001, "FAN_STATE_CHANGED", {STATE = "AUTO"})
Dbg("FAN BOOST = OFF")
end
if(packet[6] == 0x01) then --Flame effect is on
C4:UpdateProperty("Flame Effect", "ON")
C4:SendToProxy(1,"CLOSED",{}, "NOTIFY")
currentState = "close"
Dbg("FLAME EFFECT = ON")
else
C4:UpdateProperty("Flame Effect", "OFF")
C4:SendToProxy(1,"OPENED",{}, "NOTIFY")
Dbg("FLAME EFFECT = OFF")
currentState = "open"
end
local setPoint = Hex2Dec(string.format("%x",packet[7])) --Set point (degrees c)
C4:UpdateProperty("Current Setpoint", setPoint .. " DEGREES")
C4:SendToProxy(5001, "HEAT_SETPOINT_CHANGED", {SETPOINT = tonumber(setPoint), SCALE = "F"})
local roomTemp = Hex2Dec(string.format("%x",packet[8])) --Room temp (degrees c)
C4:UpdateProperty("Current Temperature", roomTemp .. " DEGREES")
C4:SendToProxy(5001, "TEMPERATURE_CHANGED", {TEMPERATURE = tonumber(roomTemp), SCALE = "F"})
Dbg("SETPOINT = " .. setPoint)
Dbg("ROOMTEMP = " .. roomTemp)
end
function StringTokenise(str, seperator, skipEmpty)
local start = 1
local pos = 1
local tokens = {"", ""}
local i = 1
str = string.gsub(str, " ", "")
while (str ~= nil) do
pos = string.find(str, seperator, start)
if (pos ~= nil) then
if (skipEmpty ~= true) or
(pos > start + 1) then
tokens[i] = string.sub(str, start, pos - 1)
i = i + 1
end
start = pos + 1
else
tokens[i] = string.sub(str, start)
break
end
end
return tokens
end
function returnByte(Byte)
if string.len(string.format("%x",Byte, 1)) < 2 then
return ("0" .. string.format("%x",Byte, 1))
else
return string.format("%x",Byte, 1)
end
end
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< DEBUG FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
debugFileName = "chowmain_escea_fireplace.log" --whole filename case sensitive
-------------------------------------------------------------------
--Function Name : Dbg(debugString)
--Parameters : debugString(string)
--Description : Function called to output debug
-------------------------------------------------------------------
function Dbg(debugString)
local dbgStr = os.date() .. " - " .. debugString .. "\r\n\r\n"
if (debugMode == 1) then print(dbgStr)
elseif (debugMode == 2) then Dbglog(dbgStr)
elseif (debugMode == 3) then
print(dbgStr)
Dbglog(dbgStr)
end
end
-------------------------------------------------------------------
--Function Name : Dbglog(debugString)
--Parameters : debugString(string)
--Description : Function called to output debug to file
-------------------------------------------------------------------
function Dbglog(debugString, ...)
C4:FileSetDir("/var/log/") -- Set file path (new one now)
fh = C4:FileOpen(debugFileName) -- Attempt to open file
if (fh == -1) then print("ERROR: Debug failed log to file\r\n")
else
local fileSize = C4:FileGetSize(fh)
if (tonumber(fileSize) > 2097152) then --More then 2MB in size. LogRotate failed. Delete file.
print("LOG FILESIZE IS OVER 2MB. LOGROTATE FAILURE. DELETING FILE")
C4:FileClose(fh)
if (C4:FileDelete(debugFileName)) then
print("LOG DELETION SUCCESSFUL")
fileSize = 0
else
print("LOGF DELETION FAILURE")
end
fh = C4:FileOpen(debugFileName) -- Attempt to open file
end
C4:FileSetPos(fh, fileSize) -- Start writing from end of file
bytesWritten = C4:FileWrite(fh, string.len(debugString), debugString)
C4:FileClose(fh)
end
end
-------------------------------------------------------------------
--Function Name : DbgPad(strToPad, length)
--Parameters : strToPad(string), length(int)
--Description : Function called to pad 0's in front of string
-------------------------------------------------------------------
function DbgPad(strToPad, length)
if (string.len(strToPad) >= length) then
return strToPad
else
for i = string.len(strToPad), length -1, 1 do
strToPad = "0" .. strToPad
end
return strToPad
end
end
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LICENCE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
MASTER_NUMBER = 308
PRODUCT_NUMBER = 132
SOFTWARE_VERSION = "8.4.1"
TrialPeriodHours = 48
GeoLocationEnabled = false
GeoLocationIP = ""
GeoLocationCountryCode = ""
GeoLocationCountryName = ""
GeoLocationCountryRegionCode = ""
GeoLocationCountryRegionName = ""
GeoLocationCountryCity = ""
GeoLocationCountryZipCode = ""
GeoLocationCountryLatitude = ""
GeoLocationCountryLongitude = ""
AllowedCountryCode = {'NZ','AU'}
DisallowedCountryCode = {'NZ','AU'}
AllowedCities = {}
C4:UpdateProperty("Control4 MAC Address", C4:GetUniqueMAC())
-------------------------------------------------------------------
--Function Name : GetFormattedDate()
--Parameters :
--Description : function to return date in format MM/DD/YYYY HH:MM:
-------------------------------------------------------------------
function GetFormattedDate()
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 )).. ": ")
end
-------------------------------------------------------------------
--Function Name : DoesFileExist(filename,type)
--Parameters : filename(string),type(c4z or c4i)
--Description : Function to check if location is valid
-------------------------------------------------------------------
function Licence_HouselogixActivate()
--if(string.len(Properties["Activation Key"]) > 0) then
C4:UpdateProperty("Activation Status", "Checking key, please wait")
local postData = string.format("lic=%s&mac=%s&p=%s&ver=%s", Properties["Activation Key"], C4:GetUniqueMAC(), PRODUCT_NUMBER, SOFTWARE_VERSION)
houselogixLicenceCheck = C4:urlPost("https://w...content-available-to-author-only...x.com/license-manager/v1/", postData)
--end
end
-------------------------------------------------------------------
--Function Name : IsGeoLocationValid()
--Parameters :
--Description : Function to check if location is valid
-------------------------------------------------------------------
function Licence_IsGeoLocationValid()
if (GeoLocationEnabled == false) then return true end
for _,country in pairs(DisallowedCountryCode) do
if (GeoLocationCountryCode == country) then return false end
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - USAGE OUTSIDE OF VALID REGION")
end
for _,country in pairs(AllowedCountryCode) do
if (GeoLocationCountryCode == country) then return true end
end
for _,city in pairs(AllowedCities) do
if (GeoLocationCountryCity == city) then return true end
end
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - USAGE OUTSIDE OF VALID REGION")
return false
end
-------------------------------------------------------------------
--Function Name : GetGeoLocation()
--Parameters :
--Description : Function to get location
-------------------------------------------------------------------
function Licence_GetGeoLocation()
geoLocation = C4:urlGet("http://f...content-available-to-author-only...p.net/xml/")
end
function Licence_Is_Valid()
if(PersistData["LicenceData"] == nil) then
if(Licence_IsGeoLocationValid()) then
--Licence_Trial()
return true
else
return false
end
elseif(PersistData["LicenceData"]["State"] == "TRIAL") then
if(Licence_IsGeoLocationValid()) then
return true
else
return false
end
elseif(PersistData["LicenceData"]["State"] == "VALID") then
if(Licence_IsGeoLocationValid()) then
return true
else
return false
end
elseif(PersistData["LicenceData"]["State"] == "INVALID") then
return false
end
end
function Licence_Trial_Hours_Remaining()
return TrialPeriodHours - math.floor((os.time() - PersistData["LicenceData"]["Trial Started"]) / 3600)
end
function Licence_CreateTrial()
if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {} end
if (nil == PersistData["LicenceData"]["State"]) then --new trial licence
PersistData["LicenceData"]["State"] = "TRIAL"
PersistData["LicenceData"]["Trial Started"] = os.time()
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
if(trialLicenceTimer == nil or trialLicenceTimer == 0) then trialLicenceTimer = C4:AddTimer("1","HOURS",true) end
elseif((PersistData["LicenceData"]["State"] == "VALID") and (string.len(Properties["Activation Key"]) > 0)) then --Don't do anything the state is valid
if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
else
if(Licence_Trial_Hours_Remaining() < 1) then
PersistData["LicenceData"]["State"] = "INVALID"
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL EXPIRED AND INVALID KEY")
if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
else
PersistData["LicenceData"]["State"] = "TRIAL"
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
if(trialLicenceTimer == nil or trialLicenceTimer == 0) then trialLicenceTimer = C4:AddTimer("1","HOURS",true) end
end
end
end
function Licence_ProcessTrialTimer()
if(PersistData["LicenceData"]["State"] ~= "VALID") then
if(Licence_Trial_Hours_Remaining() < 1) then
PersistData["LicenceData"]["State"] = "INVALID"
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL EXPIRED AND INVALID KEY")
if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
else
PersistData["LicenceData"]["State"] = "TRIAL"
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "TRIAL - " .. Licence_Trial_Hours_Remaining() .. " HOURS REMAINING")
end
else
if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
end
end
-------------------------------------------------------------------
--Function Name : ReceivedAsync(ticketId, strData, responseCode, tHeaders)
--Parameters : ticketId(number), strData(string), responseCode(string), tHeaders(table)
--Description : Function called by Control4 when url get command returned
-------------------------------------------------------------------
function ReceivedAsync(ticketId, strData, responseCode, tHeaders)
--LICENCING CODE GOES UNDER HERE
if(ticketId == masterLicenceCheck) then
-- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {['State'] = nil} end
-- if(string.find(strData,"Failed to get")) then --houselogix is down.
-- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - ACTIVATION SERVER NOT RESPONDING")
-- elseif string.find(strData,"Valid") then
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "ACTIVATED")
PersistData["LicenceData"]["State"] = "VALID"
-- if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
-- elseif string.find(strData,"Unauthorized") then
-- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - INVALID KEY")
-- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {} end
-- PersistData["LicenceData"]["State"] = "INVALID"
-- Licence_ProcessTrialTimer()
-- else
-- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - RECEIVED UNKNOWN RESPONSE")
-- end
return
end
if(ticketId == houselogixLicenceCheck) then
-- if(PersistData["LicenceData"] == nil) then PersistData["LicenceData"] = {['State'] = nil} end
-- if(string.find(strData,"Failed to get")) then --houselogix is down.
-- C4:UpdateProperty("Activation Status", GetFormattedDate() .. "FAILURE - ACTIVATION SERVER NOT RESPONDING")
-- elseif string.find(strData,"Valid") then
C4:UpdateProperty("Activation Status", GetFormattedDate() .. "ACTIVATED")
PersistData["LicenceData"]["State"] = "VALID"
-- if( trialLicenceTimer ) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
-- else
-- local postData = string.format("lic=%s&mac=%s&p=%s&ver=%s", Properties["Activation Key"], C4:GetUniqueMAC(), MASTER_NUMBER, SOFTWARE_VERSION)
-- masterLicenceCheck = C4:urlPost("https://w...content-available-to-author-only...x.com/license-manager/v1/", postData)
-- end
return
end
if(ticketId == geoLocation) then
if(string.find(strData,"Failed to get")) then --freegeoip.net is down. Enable the driver
GeoLocationEnabled = false
Dbg("Cannot get Geo Location. Enabling Driver for all locations")
else
local XMLdata = C4:ParseXml(strData)
if(XMLdata.Name == "Response") then --Response
for _, NodeValue in pairs(XMLdata.ChildNodes) do
if(NodeValue.Name == "IP") then GeoLocationIP = NodeValue.Value end
if(NodeValue.Name == "CountryCode") then GeoLocationCountryCode = NodeValue.Value end
if(NodeValue.Name == "CountryName") then GeoLocationCountryName = NodeValue.Value end
if(NodeValue.Name == "RegionCode") then GeoLocationCountryRegionCode = NodeValue.Value end
if(NodeValue.Name == "RegionName") then GeoLocationCountryRegionName = NodeValue.Value end
if(NodeValue.Name == "City") then GeoLocationCountryCity = NodeValue.Value end
if(NodeValue.Name == "ZipCode") then GeoLocationCountryZipCode = NodeValue.Value end
if(NodeValue.Name == "Latitude") then GeoLocationCountryLatitude = NodeValue.Value end
if(NodeValue.Name == "Longitude") then GeoLocationCountryLongitude = NodeValue.Value end
end
end
end
end
end
-------------------------------------------------------------------
--Function Name : OnDriverDestroyed()
--Parameters :
--Description : Function called when driver deleted or updated
-------------------------------------------------------------------
function OnDriverDestroyed()
--LICENCING CODE GOES UNDER HERE
--Clean up Control4 variables. This is required for EV auto update functionality
--Kill all timers. This is required for EV auto update functionality
if(houselogixLicence) then houselogixLicence = C4:KillTimer(houselogixLicence) end
if(trialLicenceTimer) then trialLicenceTimer = C4:KillTimer(trialLicenceTimer) end
end
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /LICENCE FUNCTIONS >>>>>>>>>>>>>>>>>>>>>>>>>>>
--<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
--//////////--//////////--//////////--//////////--//////////--//////////--//////////
--//////////--//////////--//////////--//////////--//////////--//////////--//////////
-- EXTRAS SETUP
--//////////--//////////--//////////--//////////--//////////--//////////--//////////
--//////////--//////////--//////////--//////////--//////////--//////////--//////////
function removeNewlines(string)
return string.gsub(string, [[\n]], "") -- remove line breaks
end
function NotifyProxy(Notification, tParams)
C4:SendToProxy(5001, Notification, tParams, "NOTIFY") -- , "NOTIFY"
end
-- Add "Flame Level" to the proxy's "Extras" tab
function SendExtrasSetupToProxy()
Dbg("SendExtrasSetupToProxy")
xml = [[
<extras_setup>
<extra>
<object type="list" id="FlameLevel" label="Flame Level" command="SET_FLAME_LEVEL" param_name="FlameLevel">
<list>
<item text="]] .. 'Level 1' .. [[" value="0"/>
<item text="]] .. 'Level 2' .. [[" value="1"/>
<item text="]] .. 'Level 3' .. [[" value="2"/>
<item text="]] .. 'Level 4' .. [[" value="3"/>
<item text="]] .. 'Level 5' .. [[" value="4"/>
</list>
</object>
</extra>
</extras_setup>
]]
NotifyProxy("EXTRAS_SETUP_CHANGED", { XML = removeNewlines(xml) })
--for k,v in pairs(C4:GetDeviceVariables(928)) do print(k, v.name, v.value) end
print(C4:GetDeviceVariable(928,1130))
end
-- Change Extras State
function SendExtrasStateToProxy(value)
xml = [[
<extras_state>
<extra>
]]
xml = xml .. [[<object id="FlameLevel" hidden="false" value="]] .. value .. [["/>]]
xml = xml .. [[
</extra>
</extras_state>
]]
NotifyProxy("EXTRAS_STATE_CHANGED", { XML = removeNewlines(xml) })
end
-- Update the Flame Level Extra when the Flame Level property changes
function PROPS.FlameLevel(value)
dbg('(PROPS.FlameLevel): ', value)
if value == '1' then
SendExtrasStateToProxy(0)
elseif value == '2' then
SendExtrasStateToProxy(1)
end
end
function removeNewlines(string)
return string.gsub(string, [[\n]], "") -- remove line breaks
end
for k,v in pairs(Properties) do
OnPropertyChanged(k)
end