-- FIBARO HC2 + DEKODER TELEWIZJI NC PLUS
-- http://fibaro.rafikel.pl (2013-2014)
-- Skrypt LUA urządzenia wirtualnego Fibaro oraz centralki
-- HC2, który całkowicie automatycznie utworzy i skonfiguruje
-- urządzenie wirtualne w centralce do obsługi dekodera
-- telewizji NC Plus (dekodery MediaBox)!
-- Potrzebne:
-- 1. Twoja nazwa urządzenia (np. Dekoder NC+).
-- 2. Adres IP oraz port (najpewniej 8080) dekodera.
-- 3. Hasło i login do centralki podane w treści skryptu.
-- Co skrypt zrobi:
-- 1. Znajdzie dekoder pod podanym adresem i pobierze z niego
-- wszelkie potrzebne dane.
-- 2. Przygotuje pełen zestaw przycisków, które będa dostępne
-- w interfejsie użytkownika oraz scenach blokowych.
-- 3. Pobierze z serwera fibaro.rafikel.pl oraz wgra do
-- centralki zestaw ikonek graficznych dla urządzenia.
-- 4. Utworzy zmienna globalną, która odzwierciedlać będzie
-- stan dekodera (nazwa zmiennej na podstawie podanej nazwy
-- urządzenia wirtualnego).
-- 5. W pełni umożliwi na sterowanie i odczytywanie stanu
-- dekodera z poziomu scen blokowych.
-- Instrukcja:
-- 1. Utwórz nowe urządzenie wirtualne.
-- 2. Podaj swoją nazwę (np. "Dekoder NC+") oraz adres IP
-- i port TCP dekodera (najpewniej 8080).
-- 3. Wklej zawartość skryptu do głównej pętli.
-- 4. Zapisz urządzenie wirtualne i poczekaj około minuty,
-- możesz obserwować postęp w "debugu".
-- 5. Jeśli wszystko poszło ok, odświeżając stronę zobaczysz
-- gotowe urządzenie do sterowania!
-- Możesz potem definiować własną listę ulubionych kanałów
-- poprzez dodawanie kolejnych przycisków (za przykładowym
-- Discovery na końcu).
-- DANE LOGOWANIA DO CENTRALKI
-- Te dane są obowiązkowe! Zapewniają poprawną pracę skryptu!
-- Skrypt tworzy i dostosowuje zawartość urządzenia wirtualnego
-- dla swoich potrzeb - przyciski, zmienne globalne, ikonki...
-- Aby mieć taką możliwość, potrzebuje pełnego dostępu do HC2!
USER = "admin"
PASSWORD = "admin"
-- AUTO_UPDATE = 0 | 1 [Domyślnie = 1]
-- Ustawienia automatycznej aktualizacji skryptu - zezwala na
-- automatyczne pobieranie nowych wersji skryptu z serwera
-- fibaro.rafikel.pl. Skrypt nie wysyła żadnych danych, jedynie
-- odczytuje plik na serwerze i w razie potrzeby podmienia swoją
-- zawartość na nową. Konfiguracja użytkownika nie jest nadpisywana.
AUTO_UPDATE = 1
-- PROBE_AT_START = 0 | 1 [Domyślnie = 1]
-- Sprawdzanie stanu włączenia dekodera przy uruchomieniu
-- skryptu. Jest to zrealizowane poprzez wybranie przycisków
-- Prog+ i Prog-, co pozwola na odebranie stanu z dekodera.
PROBE_AT_START = 1
-- WAIT_TIME_AFTER_CHANGES = 10..60 [Domyślnie = 30]
-- Czas w sekundach zanim skrypt wystartuje ponownie po każdym
-- etapie autokonfiguracji lub inicjalizacji. Do normalnej
-- pracy wystarczy 5 sekund, lub mniej. Na początku pozostaw
-- jednak 30 sekund i upewnij się, że wszystko działa!
WAIT_TIME_AFTER_CHANGES = 30
-- KONIEC KONFIGURACJI UŻYTKOWNIKA!
-- Poniżej znajduje się już tylko kod, który wykonuje wszystko
-- automatycznie, bez potrzeby wgłębiania się w jego strukturę.
-- Jeśli jednak czujesz się na siłach i chciałbyś go wykorzystać,
-- zmienić lub dostosować do własnych potrzeb, bardzo Cię proszę
-- o udostępnienie tych zmian dla innych użytkowników!
--[[NC_PLUS
pl.rafikel.fibaro.ncplus
]]--
VERSION = "{1_2_0}"
--[[
HISTORY
1.2.0 (2015-02-18)
- update for HC2 4.0.
1.1.0
- fixed state on global variable
1.0.9
- optimalize script and prepare functions
for usage in another scripts.
1.0.4
- automatic reconnect to NC.
1.0.3
- fixed restart problem.
1.0.2
- changed order of inicjalization procudere.
1.0.1
- fixed communication problem with NC.
]]--
--[[
EXTRA FUNCTIONS
]]--
-- counting elements in array (table)
function count(tab) i = 0; for k, v in pairs(tab) do i = i + 1; end return i; end
-- xor bits operation
function bxor(a, b) r = 0; for i = 0, 31 do x = a / 2 + b / 2; if (x~=math.floor(x)) then r = r + 2^i; end a = math.floor(a / 2); b = math.floor(b / 2); end return r; end
-- calculate checksum
function checkSum(t) c = 0; for i = 1, #t do b = string.byte(t, i); if (c==0) then c = b; else c = bxor(c, b); end if (i>100) then break; end end return c; end
-- encoding to base64
function encode(data) local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' return((data:gsub('.',function(x) local r,b='',x:byte() for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0')end return r end)..'0000'):gsub('%d%d%d?%d?%d?%d?',function(x) if(#x<6) then return('') end local c=0 for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end return b:sub(c+1,c+1) end)..({'','==','='})[#data%3+1]) end
-- random string
function random(nums) r=""; for i=1,nums do r=r..tostring(math.random(0,9)); end; return r; end
--[[
READ THIS VIRTUAL DEVICE (id, ip, port)
--]]
function readVirtualDevice(tcp)
-- generate random string
rnd = random(32);
-- send random string
fibaro:log(rnd);
-- grab virtual devices list from api
response, status, errorCode = tcp:GET("/api/virtualDevices");
-- show status on debug window
--fibaro:debug("Status of reqest: " .. status .. '.');
-- if answer is wrong
if (tonumber(status)~=200) then
fibaro:log("");
--fibaro:debug("Error " .. errorCode .. ".");
return nil, nil, nil, nil;
-- if answer is ok?
else
-- decode text to json object
jsonTable = json.decode(response);
-- roll over all virtual devices
--fibaro:debug("Checking configuration...");
for virtualIndex, virtualData in pairs(jsonTable) do
-- fibaro:debug('Virtual Device Id [' .. virtualData.id .. ']');
-- if virtual device type and name is right?
if (virtualData.type=="virtual_device") then
check = string.find(fibaro:get(virtualData.id, "log"), rnd);
if (check and check>0) then
fibaro:log("");
id = virtualData.id;
ip = virtualData.properties.ip;
port = virtualData.properties.port;
name = virtualData.name;
icon = virtualData.properties.deviceIcon;
return id, name, ip, port, icon;
end
end
end
end
fibaro:log("");
return nil;
end
--[[
GRAB FILE FROM SERVER
--]]
function getFileFromServer(tcp, path)
r, s, e = tcp:GET(path);
if (tonumber(s)~=200) then
return 0, nil;
else
return string.len(r), r;
end
return nil;
end
--[[
GET ID OF ICON MATH TO...
]]--
function getIconId(tcp, rawIcon)
-- grab icons list from api
response, status, errorCode = tcp:GET("/api/icons");
-- if answer is wrong
if (tonumber(status)~=200) then
return nil;
-- if answer is ok?
else
-- decode text to json object
jsonTable = json.decode("[" .. response .. "]");
-- look at device
iconsArray = jsonTable[1].virtualDevice;
-- check all icons
for iconIndex, iconData in pairs(iconsArray) do
if (iconData.id>=1000) then
-- grab icon from HC2
r, s, e = tcp:GET("/fibaro/n_vicons/" .. iconData.iconName .. ".png");
if (s=="200" and e==0) then
--fibaro:debug(' Icon [' .. iconData.id .. '][' .. iconData.iconName .. '][' .. string.len(r) .. ' bytes]');
if (string.len(r)==string.len(rawIcon)) then
if (checkSum(r)==checkSum(rawIcon)) then
--fibaro:debug("FOUND [" .. iconData.id .. "]!");
return iconData.id;
end
end
end
end
end
end
return 0;
end
--[[
UPLOAD ICON TO HC2
]]--
function uploadIcon(login, pass, rawIcon)
fibaro:debug("---");
fibaro:debug("Uploading icon to HC2...");
tcp = Net.FTcpSocket("localhost", 80);
if (not tcp) then
fibaro:debug("TCP CONNECTING WITH HC2 ERROR!");
return nil;
end
boundary = "WebKitFormBoundaryEy7xZuy1Trv7QrDe";
enter = "\r\n";
content =
"------" .. boundary .. enter ..
"Content-Disposition: form-data; name=\"type\"" .. enter ..
enter ..
"virtualDevice" .. enter ..
"------" .. boundary .. enter ..
"Content-Disposition: form-data; name=\"icon0\"; filename=\"icon.png\"" .. enter ..
"Content-Type: image/png" .. enter ..
enter ..
rawIcon .. enter ..
"------" .. boundary .. "--" .. enter;
tcp:write("POST /api/icons HTTP/1.1" .. enter);
tcp:write("Host: localhost" .. enter);
tcp:write("Content-Length: " .. string.len(content) .. enter);
tcp:write("Authorization: Basic " .. encode(USER..":"..PASSWORD) .. enter);
tcp:write("Content-Type: multipart/form-data; boundary=----" .. boundary .. enter .. enter);
--fibaro:debug("Sending " .. #content .. " content bytes..." .. enter);
s = 0; for i = 1, #content do
b, e = tcp:write(string.char(string.byte(content, i)));
s = s + b;
end
status, err = tcp:read();
fibaro:debug("Sended " .. s .. " content bytes with result [" .. err .. "].");
-- if answer is wrong
if (tonumber(err)>0) then
return nil;
end
-- finishing
fibaro:debug("---");
fibaro:debug("ICON SUCESSFULLY UPLOADED!");
return 0;
end
--[[
PREPARE VIRTUAL DEVICE (buttons and icons)
--]]
function prepareVirtualDevice(tcp, id, mainIcon)
-- button captions for icons
buttons = {};
-- declare key codes for icons
keys = {};
-- channel numbers for icons
channels = {};
-- value to count ready buttons
ready = 0;
-- value to count changes in buttons
changes = 0;
-- type of change to made (icons, buttons)
changeType = nil;
-- grab virtual devices list from api
response, status, errorCode = tcp:GET("/api/virtualDevices/" .. id);
-- show status on debug window
--fibaro:debug("Status of reqest: " .. status .. '.');
-- if answer is wrong
if (tonumber(status)~=200) then
fibaro:debug("Error " .. errorCode .. ".");
return nil, nil, nil, nil;
-- if answer is ok?
else
-- decode text to json object
jsonTable = json.decode("[" .. response .. "]");
-- look at device
virtualData = jsonTable[1];
-- if virtual device is right?
if (virtualData and virtualData.type=="virtual_device" and virtualData.id==id) then
-- change main icon?
if (mainIcon and mainIcon>=1000 and virtualData.properties.deviceIcon<1000) then
jsonTable[1].properties.deviceIcon = mainIcon;
changes = changes + 1;
changeType = "mainIcon";
end
-- check all rows
for rowIndex, rowData in pairs(virtualData.properties.rows) do
--fibaro:debug(' Row [' .. rowIndex .. '][' .. rowData.type .. ']');
-- if row type is button
if (rowData.type=='button') then
-- check all buttons in row
for buttonIndex, buttonData in pairs(rowData.elements) do
-- check button content
if (string.find(buttonData.msg, "KEY_")==1
or string.find(buttonData.msg, "CHANNEL_")==1
) then
-- check button icon
pos = string.find(buttonData.msg, "_");
icon = (id * 1000) + buttonData.id;
buttons[icon] = buttonData.caption;
if (string.find(buttonData.msg, "KEY_")==1) then
key = string.sub(buttonData.msg, pos + 1);
if (key and tonumber(key)) then
keys[icon] = tonumber(key);
end
elseif (string.find(buttonData.msg, "CHANNEL_")==1) then
channel = string.sub(buttonData.msg, pos + 1);
if (channel and tonumber(channel)) then
channels[icon] = tonumber(channel);
end
end
-- check icon of button
if (buttonData.buttonIcon~=icon) then
jsonTable[1].properties.rows[rowIndex].elements[buttonIndex].buttonIcon = icon;
changes = changes + 1;
changeType = "icons";
else
-- increment ready buttons
ready = ready + 1;
end
end
end -- check all buttons in row
end -- if row type is button
end -- check all rows
-- no property rows defined?
if (changes<1 and ready<1) then
-- DEFINE ALL NEW BUTTONS
jsonTable[1].properties.rows = json.decode('[{"type":"label","elements":[{"id":1,"lua":false,"waitForResponse":false,"caption":"State","name":"State","favourite":false,"main":true}]},{"type":"button","elements":[{"id":2,"lua":false,"waitForResponse":false,"caption":"⎋ Power","name":"Power","empty":false,"msg":"KEY_116","buttonIcon":0,"favourite":false,"main":true},{"id":3,"lua":false,"waitForResponse":false,"caption":"Home ⏏","name":"Home","empty":false,"msg":"KEY_174","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":4,"lua":false,"waitForResponse":false,"caption":"⇡ Vol","name":"VolUp","empty":false,"msg":"KEY_115","buttonIcon":0,"favourite":false,"main":false},{"id":5,"lua":false,"waitForResponse":false,"caption":"♬","name":"Mute","empty":false,"msg":"KEY_113","buttonIcon":0,"favourite":false,"main":false},{"id":6,"lua":false,"waitForResponse":false,"caption":"Vol ⇣","name":"VolDown","empty":false,"msg":"KEY_114","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":7,"lua":false,"waitForResponse":false,"caption":"⇡ Prog","name":"ProgUp","empty":false,"msg":"KEY_402","buttonIcon":0,"favourite":false,"main":false},{"id":8,"lua":false,"waitForResponse":false,"caption":"Prog ⇣","name":"ProgDown","empty":false,"msg":"KEY_403","buttonIcon":0,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":9,"lua":false,"waitForResponse":false,"caption":"Recorder","name":"Recorder","favourite":false,"main":false}]},{"type":"button","elements":[{"id":10,"lua":false,"waitForResponse":false,"caption":"⋘","name":"Backward","empty":false,"msg":"KEY_168","buttonIcon":0,"favourite":false,"main":false},{"id":11,"lua":false,"waitForResponse":false,"caption":"►","name":"Play","empty":false,"msg":"KEY_207","buttonIcon":0,"favourite":false,"main":false},{"id":12,"lua":false,"waitForResponse":false,"caption":"⋙","name":"Forward","empty":false,"msg":"KEY_159","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":13,"lua":false,"waitForResponse":false,"caption":"◉","name":"Rec","empty":false,"msg":"KEY_167","buttonIcon":0,"favourite":false,"main":false},{"id":14,"lua":false,"waitForResponse":false,"caption":"▮▮","name":"Pause","empty":false,"msg":"KEY_119","buttonIcon":0,"favourite":false,"main":false},{"id":15,"lua":false,"waitForResponse":false,"caption":"▇","name":"Stop","empty":false,"msg":"KEY_128","buttonIcon":0,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":16,"lua":false,"waitForResponse":false,"caption":"Cursors","name":"Cursors","favourite":false,"main":false}]},{"type":"button","elements":[{"id":17,"lua":false,"waitForResponse":false,"caption":"ⓘ Info","name":"Info","empty":false,"msg":"KEY_358","buttonIcon":0,"favourite":false,"main":false},{"id":18,"lua":false,"waitForResponse":false,"caption":"△","name":"Up","empty":false,"msg":"KEY_103","buttonIcon":0,"favourite":false,"main":false},{"id":19,"lua":false,"waitForResponse":false,"caption":"Opt ⓟ","name":"Opt","empty":false,"msg":"KEY_357","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":20,"lua":false,"waitForResponse":false,"caption":"◁","name":"Left","empty":false,"msg":"KEY_105","buttonIcon":0,"favourite":false,"main":false},{"id":21,"lua":false,"waitForResponse":false,"caption":"OK","name":"OK","empty":false,"msg":"KEY_352","buttonIcon":0,"favourite":false,"main":false},{"id":22,"lua":false,"waitForResponse":false,"caption":"▷","name":"Right","empty":false,"msg":"KEY_106","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":23,"lua":false,"waitForResponse":false,"caption":"↺ Back","name":"Back","empty":false,"msg":"KEY_158","buttonIcon":0,"favourite":false,"main":false},{"id":24,"lua":false,"waitForResponse":false,"caption":"▽","name":"Down","empty":false,"msg":"KEY_108","buttonIcon":0,"favourite":false,"main":false},{"id":25,"lua":false,"waitForResponse":false,"caption":"Text ☷","name":"Text","empty":false,"msg":"KEY_388","buttonIcon":0,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":26,"lua":false,"waitForResponse":false,"caption":"Numeric","name":"Numeric","favourite":false,"main":false}]},{"type":"button","elements":[{"id":27,"lua":false,"waitForResponse":false,"caption":"1 [.,-]","name":"Num1","empty":false,"msg":"KEY_2","buttonIcon":0,"favourite":false,"main":false},{"id":28,"lua":false,"waitForResponse":false,"caption":"2 [abc]","name":"Num2","empty":false,"msg":"KEY_3","buttonIcon":0,"favourite":false,"main":false},{"id":29,"lua":false,"waitForResponse":false,"caption":"3 [def]","name":"Num3","empty":false,"msg":"KEY_4","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":30,"lua":false,"waitForResponse":false,"caption":"4 [ghi]","name":"Num4","empty":false,"msg":"KEY_5","buttonIcon":0,"favourite":false,"main":false},{"id":31,"lua":false,"waitForResponse":false,"caption":"5 [jkl]","name":"Num5","empty":false,"msg":"KEY_6","buttonIcon":0,"favourite":false,"main":false},{"id":32,"lua":false,"waitForResponse":false,"caption":"6 [mno]","name":"Num6","empty":false,"msg":"KEY_7","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":33,"lua":false,"waitForResponse":false,"caption":"7 [pqrs]","name":"Num7","empty":false,"msg":"KEY_8","buttonIcon":0,"favourite":false,"main":false},{"id":34,"lua":false,"waitForResponse":false,"caption":"8 [tuv]","name":"Num8","empty":false,"msg":"KEY_9","buttonIcon":0,"favourite":false,"main":false},{"id":35,"lua":false,"waitForResponse":false,"caption":"9 [wxyz]","name":"Num9","empty":false,"msg":"KEY_10","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":36,"lua":false,"waitForResponse":false,"caption":"[#]","name":"NumHash","empty":false,"msg":"KEY_1","buttonIcon":0,"favourite":false,"main":false},{"id":37,"lua":false,"waitForResponse":false,"caption":"0 [ ]","name":"Num0","empty":false,"msg":"KEY_11","buttonIcon":0,"favourite":false,"main":false},{"id":38,"lua":false,"waitForResponse":false,"caption":"[*]","name":"NumStar","empty":false,"msg":"KEY_12","buttonIcon":0,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":39,"lua":false,"waitForResponse":false,"caption":"Functions","name":"Functions","favourite":false,"main":false}]},{"type":"button","elements":[{"id":40,"lua":false,"waitForResponse":false,"caption":"EPG","name":"EPG","empty":false,"msg":"KEY_365","buttonIcon":0,"favourite":false,"main":false},{"id":41,"lua":false,"waitForResponse":false,"caption":"VOD","name":"VOD","empty":false,"msg":"KEY_361","buttonIcon":0,"favourite":false,"main":false},{"id":42,"lua":false,"waitForResponse":false,"caption":"APP","name":"APP","empty":false,"msg":"KEY_367","buttonIcon":0,"favourite":false,"main":false},{"id":43,"lua":false,"waitForResponse":false,"caption":"LIST","name":"LIST","empty":false,"msg":"KEY_395","buttonIcon":0,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":44,"lua":false,"waitForResponse":false,"caption":"Channels","name":"Channels","favourite":false,"main":false}]},{"type":"slider","elements":[{"id":45,"lua":true,"waitForResponse":false,"caption":"Channel","name":"Channel","msg":"","buttonIcon":0,"value":0,"favourite":false,"main":true}]},{"type":"button","elements":[{"id":46,"lua":false,"waitForResponse":false,"caption":"TVN 24","name":"TVN24","empty":false,"msg":"CHANNEL_6","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":47,"lua":false,"waitForResponse":false,"caption":"Eska TV","name":"EskaTV","empty":false,"msg":"CHANNEL_143","buttonIcon":0,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":48,"lua":false,"waitForResponse":false,"caption":"Discovery HD","name":"Discovery","empty":false,"msg":"CHANNEL_73","buttonIcon":0,"favourite":false,"main":false}]}]');
changes = count(jsonTable[1].properties.rows);
changeType = "buttons";
end -- no more to check
end -- if virtual device is right?
-- if something was changed?
if (changes>0 and changeType) then
-- show status on debug window
if (changeType=="icons") then
fibaro:debug("Icons to buttons [" .. changes .. "] assigned!");
elseif (changeType=="buttons") then
fibaro:debug("New buttons [" .. changes .. "] created!");
elseif (changeType=="mainIcon") then
fibaro:debug("Main Icon changed!");
end
-- encode json back to text
toPut = json.encode(jsonTable[1]);
-- finishing
fibaro:debug("---");
fibaro:debug("NEW CONFIGURATION READY TO SAVE!");
fibaro:debug("New session should start in a moment...");
fibaro:debug("PLEASE BE PATIENT! WAIT...");
fibaro:sleep(WAIT_TIME_AFTER_CHANGES * 1000);
fibaro:debug("...");
-- put to HC2
response, status, errorCode = tcp:PUT("/api/virtualDevices/" .. id, toPut);
-- result?
fibaro:debug("REQEST [" .. status .. "][" .. errorCode .. "][" .. string.len(response) .. "]");
-- not happend!
fibaro:abort();
-- finish
return nil, nil, nil, nil;
else
-- return tables
return ready, buttons, keys, channels;
end
end
-- return empty
return nil, nil, nil, nil;
end
--[[
SET DEVICE PARAMETER
--]]
function setVDeviceParam(tcp, id, key, value)
-- grab virtual devices list from api
response, status, errorCode = tcp:GET("/api/virtualDevices/" .. id);
-- if answer is wrong
if (tonumber(status)~=200) then
fibaro:debug("Error " .. errorCode .. ".");
return false;
end
-- decode text to json object
jsonTable = json.decode("[" .. response .. "]");
-- look at device
virtualData = jsonTable[1];
-- if virtual device is right?
if (virtualData and virtualData.type=="virtual_device" and virtualData.id==id) then
-- change param?
jsonTable[1].properties.mainLoop = value;
-- encode json back to text
toPut = json.encode(jsonTable[1]);
-- finishing
fibaro:debug("---");
fibaro:debug("NEW CONFIGURATION READY TO SAVE!");
fibaro:debug("New session should start in a moment...");
fibaro:debug("PLEASE BE PATIENT! WAIT...");
fibaro:sleep(WAIT_TIME_AFTER_CHANGES * 1000);
fibaro:debug("...");
-- put to HC2
response, status, errorCode = tcp:PUT("/api/virtualDevices/" .. id, toPut);
-- result?
fibaro:debug("REQEST [" .. status .. "][" .. errorCode .. "][" .. string.len(response) .. "]");
-- not happend!
fibaro:abort();
-- finish
return true;
end
-- return
return false;
end
--[[
PREPARE GLOBAL VARIABLE
]]--
function prepareGlobal(tcp, name)
-- prepare gName
gName = "";
for v in string.gmatch(name, "%w") do
if (gName=="") then
for sv in string.gmatch(v, "%a") do
gName = gName .. sv;
end
else
gName = gName .. v;
end
end
value = fibaro:getGlobalValue(gName);
if (value and tonumber(value)) then
return gName;
else
response, status, errorCode = tcp:POST("/api/globalVariables", "name=" .. gName .. "&value=0");
--fibaro:debug("Status of reqest: " .. status .. '.');
if (errorCode==0 and status~="400") then
fibaro:setGlobal(gName, "0");
return gName;
else
return nil;
end
end
end
--[[
SETUP
]]--
-- network resources
local tcpHC2;
local tcpDEVICE;
local tcpSERVER;
-- ip address of NC+
local virtualIP = nil;
-- port for tcp reqests
local virtualPort = nil;
-- id of virtual device
local virtualId = nil;
-- nc+ box uuid
local boxId = nil;
-- global variables
local globalName = nil;
-- icons
local iconON = 0;
local iconOFF = 0;
local iconERR = 0;
-- declare button captions for icons
local buttonAtIcon = {};
-- declare key codes for icons
local keyAtIcon = {};
-- declare channels for icons
local channelAtIcon = {};
--[[
STATUS
]]--
function setState(state, description)
-- print description
if (description) then
-- debug
fibaro:debug(description);
-- log on home screen
fibaro:log(description);
-- state label
if (virtualId and virtualId>0) then
fibaro:call(virtualId, "setProperty", "ui.State.value", description);
end
end
-- change state?
if (state and tonumber(state)) then
state = tonumber(state);
elseif (globalName) then
state = fibaro:getGlobalValue(globalName);
if (state and tonumber(state)) then
state = tonumber(state);
else
state = 0;
end
end
-- what icon number?
if (state==1) then
icon = iconON;
elseif (state==0) then
icon = iconOFF;
else
icon = iconERR;
state = 0;
end
-- set global variable
if (globalName) then
n = fibaro:getGlobalValue(globalName);
if (n and n~=tostring(state)) then
fibaro:setGlobal(globalName, tostring(state));
end
end
if (virtualId and virtualId>0) then
-- set icon
fibaro:call(virtualId, "setProperty", "currentIcon", icon);
-- clear channel
fibaro:call(virtualId, "setProperty", "ui.Channel.value", 0);
end
end
-- starting setup
setState(0, "SETUP...");
-- connect to HC2
fibaro:debug("Connecting to HC2...");
tcpHC2 = Net.FHttp("localhost", 80);
if (not tcpHC2) then
setState(-1, "HC2 ERROR!");
fibaro:abort();
end
-- authentication for HC2
tcpHC2:setBasicAuthentication(USER, PASSWORD);
-- read virtual device
fibaro:debug("Searching virtual device...");
virtualId, virtualName, virtualIP, virtualPort, virtualIcon = readVirtualDevice(tcpHC2);
if (virtualId) then
fibaro:debug('Found ID [' .. virtualId .. ']');
if (virtualName) then
fibaro:debug('Name [' .. virtualName .. ']');
end
if (virtualIcon) then
fibaro:debug('Default icon id [' .. virtualIcon .. ']');
iconON = virtualIcon;
iconOFF = virtualIcon;
iconERR = virtualIcon;
end
if (virtualIP and virtualPort) then
fibaro:debug('IP [' .. virtualIP .. ']');
fibaro:debug('Port [' .. virtualPort .. ']');
else
if (not virtualIP) then
setState(-1, 'NO IP!');
end
if (not virtualPort) then
setState(-1, 'NO PORT!');
end
fibaro:abort();
end
else
setState(-1, "ERROR! LOGIN/PASSWORD?");
fibaro:abort();
end
-- CHECK SERVER
fibaro:debug("---");
setState(0, "CHECKING SERVER...");
-- connect to server
fibaro:debug("Connecting to [fibaro.rafikel.pl]...");
tcpSERVER = Net.FHttp("fibaro.rafikel.pl", 80);
if (not tcpSERVER) then
fibaro:debug("SERVER ERROR! Skipping...");
else
fibaro:debug("---");
fibaro:debug("CHECKING FOR UPDATES...");
size, content = getFileFromServer(tcpSERVER, "/lua/ncplus.lua");
if (size>0) then
fibaro:debug("Received script [" .. size .. " bytes].");
s_ver = string.match(content, "{(.-)}");
l_ver = string.match(VERSION, "{(.-)}");
if (s_ver==nil or l_ver==nil) then
fibaro:debug("Parsing problem! Skipping auto-update...");
else
fibaro:debug("Server version [" .. s_ver .. "].");
fibaro:debug("This version [" .. l_ver .. "].");
if (s_ver and s_ver==l_ver) then
fibaro:debug("Script is up to date!");
else
fibaro:debug("NEW VERSION AVAILABLE!");
if (AUTO_UPDATE==1) then
fibaro:debug("REPLACING SCRIPT...");
content = string.gsub(content, 'USER = "admin"', 'USER = "' .. USER .. '"');
content = string.gsub(content, 'PASSWORD = "admin"', 'PASSWORD = "' .. PASSWORD .. '"');
content = string.gsub(content, 'AUTO_UPDATE = 1', 'AUTO_UPDATE = ' .. AUTO_UPDATE);
content = string.gsub(content, 'PROBE_AT_START = 1', 'PROBE_AT_START = ' .. PROBE_AT_START);
content = string.gsub(content, 'WAIT_TIME_AFTER_CHANGES = 5', 'WAIT_TIME_AFTER_CHANGES = ' .. WAIT_TIME_AFTER_CHANGES);
setVDeviceParam(tcpHC2, virtualId, "mainLoop", content);
fibaro:debug("DONE!");
else
fibaro:debug("Turn on auto-update or grab script manualy.");
end
end
end
else
fibaro:debug("Connection problem! Skipping auto-update...");
end
-- get icons from fibaro.rafikel.pl
fibaro:debug("---");
fibaro:debug("CHECKING ICONS...");
-- ON icon
iconOnSize, iconOnRaw = getFileFromServer(tcpSERVER, "/icons/nc_on.png");
if (iconOnSize and iconOnSize>0) then
fibaro:debug("[BLUE] icon [" .. iconOnSize .. " bytes]. Searching in HC2...");
id = getIconId(tcpHC2, iconOnRaw);
if (id and tonumber(id) and id<1000) then
fibaro:debug("NOT FOUND. Trying to upload icon to HC2...");
id = uploadIcon(user, password, iconOnRaw);
end
if (id and tonumber(id) and id>=1000) then
fibaro:debug("FOUNDED [" .. id .. "] ON HC2!");
iconON = id;
end
end
-- OFF icon
iconOffSize, iconOffRaw = getFileFromServer(tcpSERVER, "/icons/nc_off.png");
if (iconOffSize>0) then
fibaro:debug("[GREY] icon [" .. iconOnSize .. " bytes]. Searching in HC2...");
id = getIconId(tcpHC2, iconOffRaw);
if (id and tonumber(id) and id<1000) then
fibaro:debug("NOT FOUND. Trying to upload icon to HC2...");
id = uploadIcon(user, password, iconOffRaw);
end
if (id and tonumber(id) and id>=1000) then
fibaro:debug("FOUNDED [" .. id .. "] ON HC2!");
iconOFF = id;
end
end
-- ERROR icon
iconErrSize, iconErrRaw = getFileFromServer(tcpSERVER, "/icons/nc_err.png");
if (iconOnSize>0) then
fibaro:debug("[RED] icon [" .. iconOnSize .. " bytes]. Searchin in HC2...");
id = getIconId(tcpHC2, iconErrRaw);
if (id and tonumber(id) and id<1000) then
fibaro:debug("NOT FOUND. Trying to upload icon to HC2...");
id = uploadIcon(user, password, iconErrRaw);
end
if (id and tonumber(id) and id>=1000) then
fibaro:debug("FOUNDED [" .. id .. "] ON HC2!");
iconERR = id;
end
end
end
-- PREPARE VIRTUAL
fibaro:debug("---");
setState(0, "PREPARE VIRTUAL...");
-- Global variable
fibaro:debug("Creating global variable base on [" .. virtualName .. "]...");
globalName = prepareGlobal(tcpHC2, virtualName);
if (globalName) then
fibaro:debug("Global variable [" .. globalName .. "] is OK.");
else
setState(-1, "ERROR CREATING GLOBAL!");
fibaro:abort();
end
-- Buttons
fibaro:debug("Prepare buttons on virtual device [" .. virtualId .. "]...");
ready, buttonAtIcon, keyAtIcon, channelAtIcon = prepareVirtualDevice(tcpHC2, virtualId, iconON);
if (ready and tonumber(ready)) then
fibaro:debug("All buttons [" .. ready .. "] prepared and ready!");
else
setState(-1, "ERROR IN VIRTUAL DEVICE!");
fibaro:abort();
end
-- FINISH PREPARE
fibaro:debug("---");
--[[
PREPARE FOR MAIN LOOP
]]--
-- variables for main loop
local counter = 0;
local prevState = 0;
local prevValue = 0;
local lastContact = 0;
local keyToSend = nil;
local channelToSend = nil;
local digitToSend = 0;
local digitCode = {[0]=11, [1]=2, [2]=3, [3]=4, [4]=5, [5]=6, [6]=7, [7]=8, [8]=9, [9]=10};
--[[
SEND UPNP REQEST
]]--
function upnpReqest(tcpSocket, ip, port, upnpUrl, upnpDomain, upnpService, upnpFunction, upnpContent)
--fibaro:debug("POST " .. upnpUrl);
reqest = "\n";
reqest = reqest .. "\n";
reqest = reqest .. "\n";
reqest = reqest .. "";
reqest = reqest .. upnpContent;
reqest = reqest .. "\n";
reqest = reqest .. "\n";
reqest = reqest .. "";
--fibaro:debug("S: " .. string.len(reqest) .. "\n");
tcpSocket:write("POST " .. upnpUrl .. " HTTP/1.1\n");
tcpSocket:write("SOAPACTION: \"urn:" .. upnpDomain);
tcpSocket:write(":service:" .. upnpService .. "#" .. upnpFunction);
tcpSocket:write("\"\n");
tcpSocket:write("Content-Length: " .. string.len(reqest) .. "\n");
tcpSocket:write("CONTENT-TYPE: text/xml; charset=\"utf-8\"\n");
tcpSocket:write("HOST: " .. ip .. ":" .. port .. "\n");
tcpSocket:write("\n");
tcpSocket:write(reqest);
fibaro:sleep(100);
result, err = tcpSocket:read();
--fibaro:debug("R: " .. string.len(result));
if (err==0) then
start = string.find(result, upnpService .. "\">");
finish = string.find(result, "0 and finish>0 and finish>start) then
data = string.sub(result, (start + string.len(upnpService) + 2), (finish - 1));
--fibaro:debug("D: " .. data);
return data;
else
return 0;
end
else
return nil;
end
end
--[[
SEND KEY DOWN AND UP
]]--
function sendKey(key)
fibaro:debug("Key down... " .. key);
upnpReqest(tcpDEVICE, virtualIP, virtualPort,
"/upnpfun/ctrl/" .. boxId .. "/04",
"adbglobal.com",
"X_ADB_RemoteControl:1",
"ProcessInputEvent",
"ev=keydn,code=" .. key .. ""
);
fibaro:sleep(100);
fibaro:debug("Key up... " .. key);
upnpReqest(tcpDEVICE, virtualIP, virtualPort,
"/upnpfun/ctrl/" .. boxId .. "/04",
"adbglobal.com",
"X_ADB_RemoteControl:1",
"ProcessInputEvent",
"ev=keyup,code=" .. key .. ""
);
fibaro:sleep(100);
end
--[[
CONNECTION LOOP
]]--
while (not tcpDEVICE) do
-- repeat until no connection
setState(0, "SEARCHING NC+...");
-- connection to NC+
fibaro:debug("Getting NC+ info [" .. virtualIP .. ":" .. virtualPort .. "]...");
http = Net.FHttp(virtualIP, virtualPort);
result, state, err = http:GET("/upnpdev/");
if (err==0) then
-- getting UUID of NC+
fibaro:debug("Reading UUID...");
--fibaro:debug("R: " .. string.sub(result, 10, 13));
start = string.find(result, "li>uuid:");
fibaro:debug("S: " .. start);
finish = string.find(result, "li>urn:");
fibaro:debug("F: " .. finish);
if (start and finish and start>0 and finish>0 and finish>start) then
boxId = "uuid_" .. string.sub(result, (start + 8), (finish - 6));
fibaro:debug("Found UUID: " .. boxId);
tcpDEVICE = Net.FTcpSocket(virtualIP, virtualPort);
else
setState(-1, "CHECK IP AND PORT!");
fibaro:abort();
end
end
if (not tcpDEVICE) then
setState(-1, "CONNECTING ERROR!");
fibaro:sleep(WAIT_TIME_AFTER_CHANGES * 1000);
end
end
-- END CONNECTION LOOP
--[[
MAIN LOOP
]]--
-- off state at start
fibaro:debug("---");
fibaro:debug("---");
fibaro:debug("---");
setState(0, "STARTING...");
-- set timeout for answers from device
if (tcpDEVICE) then
tcpDEVICE:setReadTimeout(5000);
end
-- if connection, send Prog+ and Prog-
if (tcpDEVICE and PROBE_AT_START==1) then
fibaro:debug("Checking NC+ state...");
sendKey(402);
fibaro:sleep(600);
sendKey(403);
end
-- start info
setState(0, "READY!");
-- main loop while connection is good
while (tcpDEVICE) do
-- if key was sended
if (keyToSend and tonumber(keyToSend) and tonumber(keyToSend)>=0) then
fibaro:debug("Key " .. keyToSend .. " OK");
keyToSend = nil;
end
-- ask for system state id few times
response = upnpReqest(tcpDEVICE, virtualIP, virtualPort,
"/upnpfun/ctrl/" .. boxId .. "/01",
"schemas-upnp-org",
"ContentDirectory:2",
"GetSystemUpdateID",
""
);
-- search state id in response
if (response) then
state = string.match(response, "(.*)");
else
setState(nil, "No answer!");
state = nil;
end
-- check what to send in 1 sec. loop
i = 100; while (i>0) do
-- decrese mini counter
i = i - 1;
-- check icon select - button click
icon, ts = fibaro:get(virtualId, "currentIcon");
if (icon and tonumber(icon) and tonumber(icon)>0
and math.floor(tonumber(icon)/1000)==virtualId
and ts==os.time()
) then
action = "";
icon = tonumber(icon);
key = keyAtIcon[icon];
channel = channelAtIcon[icon];
buttonCaption = buttonAtIcon[icon];
setState(nil, buttonCaption);
if (key and tonumber(key)) then
keyToSend = tonumber(key);
action = "KEY_" .. keyToSend;
elseif (channel and tonumber(channel)) then
digitToSend = 4;
channelToSend = tonumber(channel);
action = "CHANNEL_" .. channelToSend;
fibaro:call(virtualId, "setProperty", "ui.Channel.value", channelToSend);
else
action = "---";
end
fibaro:debug("Action [" .. action .. "] from button.");
end
-- check slider select
channel, ts = fibaro:get(virtualId, "ui.Channel.value");
if (channel and tonumber(channel)) then
channel = tonumber(channel);
if (ts==os.time() and digitToSend==0 and channel>0) then
setState(nil, "Channel " .. channel);
fibaro:debug("Channel [" .. channel .. "] from slider.");
channelToSend = channel;
digitToSend = 4;
end
end
-- check slider select with KEY
key, ts = fibaro:get(virtualId, "ui.Key.value");
if (key and tonumber(key)) then
key = tonumber(key);
if (ts==os.time() and key>0) then
fibaro:debug("Key [" .. key .. "] from slider.");
keyToSend = key;
end
end
-- if channel to send?
if (digitToSend>0) then
key = 0;
if (channelToSend and tonumber(channelToSend)) then
channel = tonumber(channelToSend);
number = math.floor(channel / 1000);
channel = channel - (number * 1000);
if (digitToSend==4) then key = number; end
number = math.floor(channel / 100);
channel = channel - (number * 100);
if (digitToSend==3) then key = number; end
number = math.floor(channel / 10);
channel = channel - (number * 10);
if (digitToSend==2) then key = number; end
number = math.floor(channel / 1);
if (digitToSend==1) then key = number; end
digitToSend = digitToSend - 1;
end
if (digitCode[key] and tonumber(digitCode[key])) then
keyToSend = tonumber(digitCode[key]);
end
end
-- if key to send?
if (keyToSend and tonumber(keyToSend)) then
keyToSend = tonumber(keyToSend);
sendKey(keyToSend);
if (keyToSend==402 or keyToSend==403 or keyToSend==116) then
lastContact = os.time();
end
break;
end
-- wait for next step
fibaro:sleep(10);
end
-- CHECKING STATE OF NC+
counter = counter + 1000;
if (state and tonumber(state)~=prevState) then
-- if first reading
if (prevState==0) then
prevState = tonumber(state);
else
-- after 6 sec
if (counter>5000) then
-- clear remebered prev value
prevValue = 0;
fibaro:debug("[" .. counter/1000 .. "]: Clear state!");
end
-- state nil (unknown)
if (tonumber(state)==0) then
fibaro:debug("[" .. counter/1000 .. "]: Null state!");
-- after 5 sec
elseif (counter>4000) then
value = tonumber(state) - prevState;
fibaro:debug("[" .. prevValue .. "]->[" .. counter/1000 .. " s.]->[" .. value .. "]");
prevState = tonumber(state);
if (counter<20000) then
-- OFF - 8->6--->8->6->6
if (false
or (prevValue==8 and value==6)
or (prevValue==6 and value==6)
) then setState(0, "OFF [" .. value .. "]");
-- ON
elseif (false
or (value~=8 and value~=6 and prevValue~=8 and prevValue~=6)
) then setState(1, "ON [" .. value .. "]");
-- OTHER
else
setState(nil, "State [" .. value .. "]");
end
end
counter = 0;
prevValue = value;
end
end
-- last contact clear
lastContact = 0;
end
-- if last contact (prog change) is without answer in 10 sec.
if (lastContact>0 and (os.time()-lastContact)>10) then
setState(-1, "No answer!");
lastContact = 0;
end
end
-- END MAIN LOOP
-- SET ERROR STATE
setState(-1, "RESTARTING...");
-- WAIT BEFORE NEXT RUN
fibaro:sleep(WAIT_TIME_AFTER_CHANGES * 1000);
--[[NC_PLUS
pl.rafikel.fibaro.ncplus
]]