Index: /tags/0.1/I_Freebox.xml
===================================================================
--- /tags/0.1/I_Freebox.xml	(revision 3)
+++ /tags/0.1/I_Freebox.xml	(revision 3)
@@ -0,0 +1,93 @@
+<?xml version="1.0"?>
+<implementation>
+  <functions>
+	local pluginLib
+
+	function startup(lul_device)
+		luup.log("Freebox: chargement de la librairie L_Freeboxx ...")
+		if (package.path:find ("/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua", 1, true) == nil) then
+			package.path = package.path .. ";/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua"
+		end
+
+		package.loaded.L_Freebox = nil
+		pluginLib = require("L_Freebox")
+		if (package.loaded.L_Freebox == nil)
+		then
+			luup.log("Freebox: le plugin n'est pas correctement installe. La librairie L_Freebox ne peut pas etre chargee.", 1)
+			luup.task("Plugin pas correctement installe", 2, "Freebox", -1)
+			return false
+		end
+
+		luup.log("Freebox: Librairie L_Freebox chargee")
+
+		return pluginLib.startup(lul_device)
+	end
+
+	function clearTask(data)
+		if (pluginLib ~= nil) then pluginLib.clearTask(data) end
+	end
+
+	function deferredFreeboxStartup(data)
+		if (pluginLib ~= nil) then pluginLib.deferredStartup(data) end
+	end
+
+	function refreshData()
+		if (pluginLib ~= nil) then pluginLib.refreshData() end
+	end
+  </functions>
+
+  <startup>startup</startup>
+
+  <actionList>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>Authentification</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.getAppToken() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>ModifierFrequenceMiseAJour</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.setRefreshRate(lul_settings.Frequence) end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>ActiverWifi</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.enableWifi() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>DesactiverWifi</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.disableWifi() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>ActiverFtp</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.enableFtp() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>DesactiverFtp</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.disableFtp() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>ActiverPartageFichiersWin</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.enableSambaFiles() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>DesactiverPartageFichiersWin</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.disableSambaFiles() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>ActiverPartageImprimantesWin</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.enableSambaPrinters() end</run>
+    </action>
+    <action>
+  		<serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+  		<name>DesactiverPartageImprimantesWin</name>
+  		<run>if (pluginLib ~= nil) then pluginLib.disableSambaPrinters() end</run>
+    </action>
+    </actionList>
+</implementation>
Index: /tags/0.1/L_Freebox.lua
===================================================================
--- /tags/0.1/L_Freebox.lua	(revision 3)
+++ /tags/0.1/L_Freebox.lua	(revision 3)
@@ -0,0 +1,722 @@
+module("L_Freebox", package.seeall)
+
+
+local socket = require("socket")
+local http = require("socket.http")
+local ltn12 = require("ltn12")
+local json = require("json")
+local sha1 = require("sha1")
+
+
+local PLUGIN_VERSION = "0.1"
+
+local THIS_DEVICE = 0
+
+
+local appToken = ""
+local sessionToken = ""
+local baseUrl = ""
+local serverIp = "mafreebox.freebox.fr"
+local logged = false
+local settingsPerm = false
+local refreshRate = 0
+
+local PLUGIN_SID = "urn:freebox-fr:serviceId:Freebox1"
+
+local taskHandle = -1
+local TASK_ERROR = 2
+local TASK_ERROR_PERM = -2
+local TASK_SUCCESS = 4
+local TASK_BUSY = 1
+
+
+local function log(text, level)
+	luup.log("Freebox: " .. text, (level or 50))
+end
+
+
+local function task(text, mode)
+	if (mode == TASK_ERROR_PERM)
+	then
+		log(text, 1)
+	elseif (mode ~= TASK_SUCCESS)
+	then
+		log(text, 2)
+	else
+		log(text)
+	end
+	if (mode == TASK_ERROR_PERM)
+	then
+		taskHandle = luup.task(text, TASK_ERROR, "Freebox", taskHandle)
+	else
+		taskHandle = luup.task(text, mode, "Freebox", taskHandle)
+
+		-- Clear the previous error, since they're all transient
+		if (mode ~= TASK_SUCCESS)
+		then
+			luup.call_delay("clearTask", 15, "", false)
+		end
+	end
+end
+
+function clearTask()
+	task("Clearing...", TASK_SUCCESS)
+end
+
+
+local function setVarIfChanged(serviceId, name, value, deviceId, default)
+	local curValue = luup.variable_get(serviceId, name, deviceId)
+
+	if ((value ~= curValue) or (curValue == nil)) then
+		luup.variable_set(serviceId, name, value, deviceId)
+		return true
+	else
+		return (default or false)
+	end
+end
+
+
+local function setAttrIfChanged(name, value, deviceId, default)
+	local curValue = luup.attr_get(name, deviceId)
+
+	if ((value ~= curValue) or (curValue == nil)) then
+		luup.attr_set(name, value, deviceId)
+		return true
+	else
+		return (default or false)
+	end
+end
+
+
+local sock = function()
+	local s = socket.tcp()
+	s:settimeout(2)
+	return s
+end
+
+
+local function getData(request, code, result)
+	local data = {}
+	if (request == nil and code ~= "closed") then
+		data.success = false
+		data.msg = "Freebox Revolution non joignable"
+		data.error_code = code
+	elseif (code ~= 200 and code ~= 403) then
+		data.success = false
+		data.msg = "Echec de la communication avec la Freebox Revolution"
+		data.error_code = code
+	else
+		--log(result)
+		data = json.decode(result)
+		if (data.success == nil) then
+			data.success = true
+		end
+	end
+	return data
+end
+
+
+local function getApiBaseUrl()
+	local baseUrl = ""
+	local url = "http://mafreebox.freebox.fr/api_version"
+	local result = {}
+	local request, code = http.request({
+		url = url,
+		create = sock,
+		sink = ltn12.sink.table(result)
+	})
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		baseUrl = "http://%s" .. data.api_base_url .. "v" .. string.match(data.api_version, "%d+")
+		--log("baseUrl " .. baseUrl)
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+	end
+	return baseUrl
+end
+
+
+local function restoreAppToken()
+	local fh = io.open("/etc/.freebox", "r")
+	if (fh == nil) then
+		appToken = ""
+	else
+		appToken = fh:read("*all")
+		fh:close()
+	end
+end
+
+local function saveAppToken(token)
+	local fh = io.open("/etc/.freebox", "w+")
+	fh:write(token)
+	fh:close()
+	restoreAppToken()
+end
+
+
+function getAppToken()
+	local token = ""
+	local statusAuth = "Non authentifie ; appuyer sur le bouton " .. '"' .. "s'authentifier" .. '"'
+
+	if (baseUrl == "") then
+		statusAuth = "Freebox Revolution non trouve sur le reseau"
+	else
+		log("Demande d'authentification ...")
+		local url = string.format(baseUrl, serverIp) .. "/login/authorize"
+		local requestData = '{ "app_id": "vera.plugin", "app_name": "Freebox Vera Plugin", "app_version": "0.1", "device_name": "Vera" }'
+		local result = {}
+		local request, code = http.request({
+			url = url,
+			create = sock,
+			method = "POST",
+			headers = { ["Content-Type"] = "application/json",
+						["Content-Length"] = requestData:len() },
+			source = ltn12.source.string(requestData),
+			sink = ltn12.sink.table(result)
+		})
+		local data = getData(request, code, table.concat(result))
+		if (data.success) then
+			token = data.result.app_token
+			local trackId = data.result.track_id
+			--log("token " .. token)
+			--log("trackId " .. trackId)
+
+			local status
+			repeat
+				status = "error"
+				url = string.format(baseUrl, serverIp) .. "/login/authorize/" .. trackId
+				result = {}
+				request, code = http.request({
+					url = url,
+					create = sock,
+					sink = ltn12.sink.table(result)
+				})
+				data = getData(request, code, table.concat(result))
+				if (data.success) then
+					status = data.result.status
+					--log("status " .. status)
+					--log("challenge " .. data.result.challenge)
+				else
+					log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+				end
+				if (status == "pending") then
+					log("Authentification en cours ...")
+					luup.sleep(2000)
+				end
+			until status ~= "pending"
+			log(status)
+			if (status == "granted") then
+				log("Authentification ok")
+				statusAuth = "Authentifie"
+			else
+				log("Authentification KO")
+				token = ""
+			end
+		else
+			log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+		end
+	end
+	saveAppToken(token)
+	setVarIfChanged(PLUGIN_SID, "StatusAuthentification", statusAuth, THIS_DEVICE, false)
+end
+
+
+local function openSession()
+	logged = false
+	local newSessionToken = ""
+	local newSettingsPerm = false
+	local challenge = nil
+	local statusAuth = "Non authentifie ; appuyer sur le bouton " .. '"' .. "s'authentifier" .. '"'
+	local statusConfig
+	if (settingsPerm) then
+		statusConfig = "Autorise"
+	else
+		statusConfig = "Non autorise"
+	end
+
+	if (baseUrl == "") then
+		statusAuth = "Freebox Revolution non trouve sur le reseau"
+	elseif (appToken ~= "") then
+		local url = string.format(baseUrl, serverIp) .. "/login"
+		local result = {}
+		local headers = {}
+		if (sessionToken ~= "") then
+			headers["X-Fbx-App-Auth"] = sessionToken
+		end
+		local request, code = http.request({
+			url = url,
+			create = sock,
+			headers = headers,
+			sink = ltn12.sink.table(result)
+		})
+		local data = getData(request, code, table.concat(result))
+		if (data.success) then
+			logged = data.result.logged_in
+			challenge = data.result.challenge
+		else
+			log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+		end
+
+		if (not logged and challenge ~= nil) then
+			log("Demande de session ...")
+			local password = sha1.hmac_sha1(appToken, challenge)
+			url = string.format(baseUrl, serverIp) .. "/login/session"
+			local requestData = '{ "app_id": "vera.plugin", "password": "' .. password .. '" }'
+			result = {}
+			request, code = http.request({
+				url = url,
+				create = sock,
+				method = "POST",
+				headers = { ["Content-Type"] = "application/json",
+							["Content-Length"] = requestData:len() },
+				source = ltn12.source.string(requestData),
+				sink = ltn12.sink.table(result)
+			})
+			data = getData(request, code, table.concat(result))
+			if (data.success) then
+				log("Session ok")
+				newSessionToken = data.result.session_token
+				newSettingsPerm = data.result.permissions.settings
+				-- log("newSessionToken " .. newSessionToken)
+				-- if (newSettingsPerm) then
+					-- log("permission ok")
+				-- else
+					-- log("permission ko")
+				-- end
+			else
+				log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+				log("Session KO")
+				if (data.error_code == "invalid_token") then
+					saveAppToken("")
+				end
+			end
+
+			if (newSessionToken ~= "") then
+				url = string.format(baseUrl, serverIp) .. "/login"
+				result = {}
+				request, code = http.request({
+					url = url,
+					create = sock,
+					headers = { ["X-Fbx-App-Auth"] = newSessionToken },
+					sink = ltn12.sink.table(result)
+				})
+				data = getData(request, code, table.concat(result))
+				if (data.success) then
+					logged = data.result.logged_in
+				else
+					log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+				end
+			end
+		end
+	end
+
+	if (newSessionToken ~= "") then
+		sessionToken = newSessionToken
+		settingsPerm = newSettingsPerm
+	end
+	if (logged) then
+		statusAuth = "Authentifie et connecte"
+		if (settingsPerm) then
+			statusConfig = "Autorise"
+		else
+			statusConfig = "Non autorise"
+		end
+	end
+	setVarIfChanged(PLUGIN_SID, "StatusAuthentification", statusAuth, THIS_DEVICE, false)
+	setVarIfChanged(PLUGIN_SID, "StatusConfigFreebox", statusConfig, THIS_DEVICE, false)
+end
+
+
+local function checkLanServer(checkSession)
+	if (checkSession) then
+		openSession()
+		if (not logged) then
+			--log("non connecté", 2)
+			return
+		end
+	end
+
+	local url = string.format(baseUrl, serverIp) .. "/lan/config"
+	local result = {}
+	local request, code = http.request({
+		url = url,
+		create = sock,
+		headers = { ["X-Fbx-App-Auth"] = sessionToken },
+		sink = ltn12.sink.table(result)
+	})
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		if (data.result.name ~= nil and data.result.name ~= "") then
+			--log("name " .. data.result.name)
+			setAttrIfChanged("name", data.result.name, THIS_DEVICE, false)
+		end
+		if (data.result.ip ~= nil and data.result.ip ~= "") then
+			--log("ip " .. data.result.ip)
+			setAttrIfChanged("ip", data.result.ip, THIS_DEVICE, false)
+			serverIp = data.result.ip
+		end
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+	end
+end
+
+
+local function checkNetworkDevices(checkSession)
+	local devices = {}
+
+	if (checkSession) then
+		openSession()
+		if (not logged) then
+			--log("non connecté", 2)
+			return
+		end
+	end
+
+	local url = string.format(baseUrl, serverIp) .. "/lan/browser/interfaces"
+	local result = {}
+	local request, code = http.request({
+		url = url,
+		create = sock,
+		headers = { ["X-Fbx-App-Auth"] = sessionToken },
+		sink = ltn12.sink.table(result)
+	})
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		local interfaceName = data.result[1].name
+		local count = data.result[1].host_count
+		--log("name " .. interfaceName)
+		--log("host_count " .. count)
+		if (count > 0) then
+			url = string.format(baseUrl, serverIp) .. "/lan/browser/" .. interfaceName
+			result = {}
+			request, code = http.request({
+				url = url,
+				create = sock,
+				headers = { ["X-Fbx-App-Auth"] = sessionToken },
+				sink = ltn12.sink.table(result)
+			})
+			data = getData(request, code, table.concat(result))
+			if (data.success) then
+				for i=1,count do
+					local id = data.result[i].id
+					devices[id] = {}
+					devices[id].name = data.result[i].primary_name
+					local ip = nil
+					if (data.result[i].l3connectivities ~= nil) then
+						for j=1,5 do
+							if (data.result[i].l3connectivities[j] == nil) then
+								break
+							end
+							if (data.result[i].l3connectivities[j].af == "ipv4" or ip == nil) then
+								ip = data.result[i].l3connectivities[j].addr
+							end
+						end
+					end
+					if (ip ~= nil) then
+						devices[id].ip = ip
+					else
+						devices[id].ip = "undefined ip"
+					end
+					devices[id].reachable = data.result[i].reachable
+				end
+			else
+				log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+			end
+		end
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+	end
+
+	-- for k, v in pairs(devices) do
+		-- if (v.reachable) then
+			-- log(v.name .. " " .. v.ip .. " joignable")
+		-- else
+			-- log(v.name .. " " .. v.ip .. " injoignable")
+		-- end
+	-- end
+end
+
+
+function wifi(checkSession, requestData)
+	if (checkSession) then
+		openSession()
+		if (not logged) then
+			--log("non connecté", 2)
+			return
+		end
+	end
+
+	local url = string.format(baseUrl, serverIp) .. "/wifi/config"
+	local request, code
+	local result = {}
+	if (requestData ~= nil and requestData ~= "") then
+		request, code = http.request({
+			url = url,
+			create = sock,
+			method = "PUT",
+			headers = { ["X-Fbx-App-Auth"] = sessionToken,
+						["Content-Type"] = "application/json",
+						["Content-Length"] = requestData:len() },
+			source = ltn12.source.string(requestData),
+			sink = ltn12.sink.table(result)
+		})
+	else
+		request, code = http.request({
+			url = url,
+			create = sock,
+			headers = { ["X-Fbx-App-Auth"] = sessionToken },
+			sink = ltn12.sink.table(result)
+		})
+	end
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		if (data.result.ap_params.enabled) then
+			--log("Wifi actif")
+			setVarIfChanged(PLUGIN_SID, "WifiActif", "1", THIS_DEVICE, false)
+		else
+			--log("Wifi inactif")
+			setVarIfChanged(PLUGIN_SID, "WifiActif", "0", THIS_DEVICE, false)
+		end
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+		task(data.msg, TASK_ERROR)
+	end
+end
+
+
+local function checkWifi(checkSession)
+	wifi(checkSession, "")
+end
+
+
+function enableWifi()
+	wifi(true, '{ "ap_params": { "enabled": true } }')
+end
+
+
+function disableWifi()
+	wifi(true, '{ "ap_params": { "enabled": false } }')
+end
+
+
+function ftp(checkSession, requestData)
+	if (checkSession) then
+		openSession()
+		if (not logged) then
+			--log("non connecté", 2)
+			return
+		end
+	end
+
+	local url = string.format(baseUrl, serverIp) .. "/ftp/config"
+	local request, code
+	local result = {}
+	if (requestData ~= nil and requestData ~= "") then
+		request, code = http.request({
+			url = url,
+			create = sock,
+			method = "PUT",
+			headers = { ["X-Fbx-App-Auth"] = sessionToken,
+						["Content-Type"] = "application/json",
+						["Content-Length"] = requestData:len() },
+			source = ltn12.source.string(requestData),
+			sink = ltn12.sink.table(result)
+		})
+	else
+		request, code = http.request({
+			url = url,
+			create = sock,
+			headers = { ["X-Fbx-App-Auth"] = sessionToken },
+			sink = ltn12.sink.table(result)
+		})
+	end
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		if (data.result.enabled) then
+			--log("FTP actif")
+			setVarIfChanged(PLUGIN_SID, "FtpActif", "1", THIS_DEVICE, false)
+		else
+			--log("FTP inactif")
+			setVarIfChanged(PLUGIN_SID, "FtpActif", "0", THIS_DEVICE, false)
+		end
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+		task(data.msg, TASK_ERROR)
+	end
+end
+
+
+local function checkFtp(checkSession)
+	ftp(checkSession, "")
+end
+
+
+function enableFtp()
+	ftp(true, '{ "enabled": true }')
+end
+
+
+function disableFtp()
+	ftp(true, '{ "enabled": false }')
+end
+
+
+function sambaShares(checkSession, requestData)
+	if (checkSession) then
+		openSession()
+		if (not logged) then
+			--log("non connecté", 2)
+			return
+		end
+	end
+
+	local url = string.format(baseUrl, serverIp) .. "/netshare/samba"
+	local request, code
+	local result = {}
+	if (requestData ~= nil and requestData ~= "") then
+		request, code = http.request({
+			url = url,
+			create = sock,
+			method = "PUT",
+			headers = { ["X-Fbx-App-Auth"] = sessionToken,
+						["Content-Type"] = "application/json",
+						["Content-Length"] = requestData:len() },
+			source = ltn12.source.string(requestData),
+			sink = ltn12.sink.table(result)
+		})
+	else
+		request, code = http.request({
+			url = url,
+			create = sock,
+			headers = { ["X-Fbx-App-Auth"] = sessionToken },
+			sink = ltn12.sink.table(result)
+		})
+	end
+	local data = getData(request, code, table.concat(result))
+	if (data.success) then
+		if (data.result.file_share_enabled) then
+			--log("Partage fichiers Windows actif")
+			setVarIfChanged(PLUGIN_SID, "PartageFichiersWinActif", "1", THIS_DEVICE, false)
+		else
+			--log("Partage fichiers Windows inactif")
+			setVarIfChanged(PLUGIN_SID, "PartageFichiersWinActif", "0", THIS_DEVICE, false)
+		end
+		if (data.result.print_share_enabled) then
+			--log("Partage imprimantes Windows actif")
+			setVarIfChanged(PLUGIN_SID, "PartageImprimantesWinActif", "1", THIS_DEVICE, false)
+		else
+			--log("Partage imprimantes Windows inactif")
+			setVarIfChanged(PLUGIN_SID, "PartageImprimantesWinActif", "0", THIS_DEVICE, false)
+		end
+	else
+		log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
+		task(data.msg, TASK_ERROR)
+	end
+end
+
+
+local function checkSambaShares(checkSession)
+	sambaShares(checkSession, "")
+end
+
+
+function enableSambaFiles()
+	sambaShares(true, '{ "file_share_enabled": true }')
+end
+
+
+function disableSambaFiles()
+	sambaShares(true, '{ "file_share_enabled": false }')
+end
+
+
+function enableSambaPrinters()
+	sambaShares(true, '{ "print_share_enabled": true }')
+end
+
+
+function disableSambaPrinters()
+	sambaShares(true, '{ "print_share_enabled": false }')
+end
+
+
+local function getRefreshRate()
+	local rate = tonumber(luup.variable_get(PLUGIN_SID, "FrequenceMiseAJour", THIS_DEVICE) or "")
+	if (rate == nil or rate < 5) then
+		rate = 0
+		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", rate, THIS_DEVICE, false)
+	end
+	refreshRate = rate
+end
+
+
+function setRefreshRate(rate)
+	rate = tonumber(rate)
+	if (rate ~= nil) then
+		if (rate < 5) then
+			rate = 0
+		end
+		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", rate, THIS_DEVICE, false)
+		local oldRate = refreshRate
+		refreshRate = rate
+		if (oldRate < 5 and refreshRate >= 5) then
+			refreshData()
+		end
+	end
+end
+
+
+-- Function called at plugin startup
+function startup(lul_device)
+
+	THIS_DEVICE = lul_device
+
+	task("Démarrage plugin Freebox", TASK_SUCCESS)
+	
+	luup.call_delay("deferredFreeboxStartup", 1)
+
+	task("Plugin Freebox ok", TASK_SUCCESS)
+	
+	return true
+
+end
+
+
+function deferredStartup(data)
+
+	setVarIfChanged(PLUGIN_SID, "PluginVersion", PLUGIN_VERSION, THIS_DEVICE, false)
+
+	baseUrl = getApiBaseUrl()
+
+	restoreAppToken()
+
+	getRefreshRate()
+
+	refreshData()
+
+end
+
+
+function refreshData()
+	--log("mise à jour des données")
+
+	if (refreshRate >= 5) then
+		luup.call_delay("refreshData", refreshRate)
+	end
+
+	openSession()
+	if (not logged) then
+		--log("non connecté", 2)
+		return
+	end
+
+	checkLanServer(false)
+
+	--checkNetworkDevices(false)
+
+	checkWifi(false)
+
+	checkFtp(false)
+
+	checkSambaShares(false)
+end
Index: /tags/0.1/D_Freebox.json
===================================================================
--- /tags/0.1/D_Freebox.json	(revision 3)
+++ /tags/0.1/D_Freebox.json	(revision 3)
@@ -0,0 +1,446 @@
+{
+    "flashicon": "http://t0.gstatic.com/images?q=tbn:ANd9GcRGh4fNzwueLZvpdS_F7x6fnc-ybolpFnmVj6csFTFnCojEH5MzZA",
+    "imgIconBody": "",
+    "imgIconDimmable": "",
+    "imgIconTurnable": "",
+    "imgIconMin": "",
+    "imgIconMax": "",
+    "halloIconsDir": "pics/hallo",
+    "inScene": "0",
+    "DisplayStatus": {
+    },
+    "doc_url": {
+        "doc_language": 1,
+        "doc_manual": 1,
+        "doc_version": 1,
+        "doc_platform": 0,
+        "doc_page": "sensors"
+    },
+    "Tabs": [
+        {
+            "Label": {
+                "lang_tag": "tabname_control",
+                "text": "Control"
+            },
+            "Position": "0",
+            "TabType": "flash",
+            "ControlGroup": [
+                {
+                    "id": "1",
+                    "scenegroup": "1"
+                }
+            ],
+            "SceneGroup": [
+                {
+                    "id": "1",
+                    "top": "1.5",
+                    "left": "0.25",
+                    "x": "1.5",
+                    "y": "2"
+                }
+            ],
+            "Control": [
+                {
+                    "ControlType": "label",
+                    "top": "0",
+                    "left": "0",
+                    "Label": {
+                        "lang_tag": "plugin_version",
+                        "text": "Version plugin"
+                    },
+                    "Display": {
+                        "Top": 15,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "variable",
+                    "top": "1",
+                    "left": "0",
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "PluginVersion",
+                        "Top": 15,
+                        "Left": 135,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_authentication",
+                        "text": "S'authentifier"
+                    },
+                    "Display": {
+                        "Top": 40,
+                        "Left": 25,
+                        "Width": 100,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "Authentification", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "variable",
+                    "top": "1",
+                    "left": "0",
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "StatusAuthentification",
+                        "Top": 35,
+                        "Left": 135,
+                        "Width": 200,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "top": "0",
+                    "left": "0",
+                    "Label": {
+                        "lang_tag": "help_authentication",
+                        "text": "Info: l'authentification requiert une validation sur le panneau LCD de la Freebox"
+                    },
+                    "Display": {
+                        "Top": 50,
+                        "Left": 135,
+                        "Width": 200,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "refresh_rate",
+                        "text": "Frequence de mise a jour"
+                    },
+                    "Display": {
+                        "Top": 75,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "variable",
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "FrequenceMiseAJour",
+                        "Top": 75,
+                        "Left": 200,
+                        "Width": 25,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "refresh_rate_unit",
+                        "text": "secondes"
+                    },
+                    "Display": {
+                        "Top": 75,
+                        "Left": 240,
+                        "Width": 40,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "input",
+                    "ID": "Rate",
+                    "Style": "numeric",
+                    "Display":{
+                        "Top": 70,
+                        "Left": 310,
+                        "Width": 30,
+                        "Height": 20
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_set_refresh_rate",
+                        "text": "Modifier"
+                    },
+                    "Display": {
+                        "Top": 70,
+                        "Left": 350,
+                        "Width": 75,
+                        "Height": 20
+                    },
+                    "Command": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Action": "ModifierFrequenceMiseAJour",
+                        "Parameters": [
+                            {
+                                "Name": "Frequence",
+                                "ID": "Rate"
+                            }
+                        ]
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "setup",
+                        "text": "Configuration"
+                    },
+                    "Display": {
+                        "Top": 105,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "variable",
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "StatusConfigFreebox",
+                        "Top": 105,
+                        "Left": 200,
+                        "Width": 100,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "wifi",
+                        "text": "Wifi"
+                    },
+                    "Display": {
+                        "Top": 135,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_wifi_enable",
+                        "text": "Activer"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "WifiActif",
+                        "Value": "1",
+                        "Top": 130,
+                        "Left": 200,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "ActiverWifi", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_wifi_disable",
+                        "text": "Desactiver"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "WifiActif",
+                        "Value": "0",
+                        "Top": 130,
+                        "Left": 300,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "DesactiverWifi", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "ftp",
+                        "text": "FTP"
+                    },
+                    "Display": {
+                        "Top": 165,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_ftp_enable",
+                        "text": "Activer"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "FtpActif",
+                        "Value": "1",
+                        "Top": 160,
+                        "Left": 200,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "ActiverFtp", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_ftp_disable",
+                        "text": "Desactiver"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "FtpActif",
+                        "Value": "0",
+                        "Top": 160,
+                        "Left": 300,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "DesactiverFtp", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "samba_file",
+                        "text": "Partage fichiers Windows"
+                    },
+                    "Display": {
+                        "Top": 195,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_samba_file_enable",
+                        "text": "Activer"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "PartageFichiersWinActif",
+                        "Value": "1",
+                        "Top": 190,
+                        "Left": 200,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "ActiverPartageFichiersWin", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_samba_file_disable",
+                        "text": "Desactiver"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "PartageFichiersWinActif",
+                        "Value": "0",
+                        "Top": 190,
+                        "Left": 300,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "DesactiverPartageFichiersWin", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "label",
+                    "Label": {
+                        "lang_tag": "samba_printer",
+                        "text": "Partage imprimantes Windows"
+                    },
+                    "Display": {
+                        "Top": 225,
+                        "Left": 25,
+                        "Width": 75,
+                        "Height": 20 
+                    }
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_samba_file_enable",
+                        "text": "Activer"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "PartageImprimantesWinActif",
+                        "Value": "1",
+                        "Top": 220,
+                        "Left": 200,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "ActiverPartageImprimantesWin", 
+                        "Parameters": [] 
+                    } 
+                },
+                {
+                    "ControlType": "button",
+                    "Label": {
+                        "lang_tag": "cmd_samba_file_disable",
+                        "text": "Desactiver"
+                    },
+                    "Display": {
+                        "Service": "urn:freebox-fr:serviceId:Freebox1",
+                        "Variable": "PartageImprimantesWinActif",
+                        "Value": "0",
+                        "Top": 220,
+                        "Left": 300,
+                        "Width": 85,
+                        "Height": 20 
+                    },
+                    "Command": { 
+                        "Service": "urn:freebox-fr:serviceId:Freebox1", 
+                        "Action": "DesactiverPartageImprimantesWin", 
+                        "Parameters": [] 
+                    } 
+                }
+            ]
+        },
+        {
+            "Label": {
+                "lang_tag": "advanced",
+                "text": "Advanced"
+            },
+            "Position": "1",
+            "TabType": "javascript",
+            "ScriptName": "shared.js",
+            "Function": "advanced_device"
+        }
+    ],
+    "DeviceType": "urn:freebox-fr:device:Freebox:1"
+}
Index: /tags/0.1/json.lua
===================================================================
--- /tags/0.1/json.lua	(revision 3)
+++ /tags/0.1/json.lua	(revision 3)
@@ -0,0 +1,521 @@
+-----------------------------------------------------------------------------
+-- JSON4Lua: JSON encoding / decoding support for the Lua language.
+-- json Module.
+-- Author: Craig Mason-Jones
+-- Homepage: http://json.luaforge.net/
+-- Version: 0.9.50
+-- This module is released under the MIT License (MIT).
+-- Please see LICENCE.txt for details.
+--
+-- USAGE:
+-- This module exposes two functions:
+--   encode(o)
+--     Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
+--   decode(json_string)
+--     Returns a Lua object populated with the data encoded in the JSON string json_string.
+--
+-- REQUIREMENTS:
+--   compat-5.1 if using Lua 5.0
+--
+-- CHANGELOG
+--	 0.9.50 Radical performance improvement on decode from Eike Decker. Many thanks!
+--	 0.9.40 Changed licence to MIT License (MIT)
+--   0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). 
+--          Fixed Lua 5.1 compatibility issues.
+--   		Introduced json.null to have null values in associative arrays.
+--          encode() performance improvement (more than 50%) through table.concat rather than ..
+--          Introduced decode ability to ignore /**/ comments in the JSON string.
+--   0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Imports and dependencies
+-----------------------------------------------------------------------------
+local math = require('math')
+local string = require("string")
+local table = require("table")
+local tostring = tostring
+
+local base = _G
+
+-----------------------------------------------------------------------------
+-- Module declaration
+-----------------------------------------------------------------------------
+module("json")
+
+-- Public functions
+
+-- Private functions
+local decode_scanArray
+local decode_scanComment
+local decode_scanConstant
+local decode_scanNumber
+local decode_scanObject
+local decode_scanString
+local decode_scanWhitespace
+local encodeString
+local isArray
+local isEncodable
+
+-----------------------------------------------------------------------------
+-- PUBLIC FUNCTIONS
+-----------------------------------------------------------------------------
+--- Encodes an arbitrary Lua object / variable.
+-- @param v The Lua object / variable to be JSON encoded.
+-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
+function encode (v)
+  -- Handle nil values
+  if v==nil then
+    return "null"
+  end
+  
+  local vtype = base.type(v)  
+
+  -- Handle strings
+  if vtype=='string' then    
+    return '"' .. encodeString(v) .. '"'	    -- Need to handle encoding in string
+  end
+  
+  -- Handle booleans
+  if vtype=='number' or vtype=='boolean' then
+    return base.tostring(v)
+  end
+  
+  -- Handle tables
+  if vtype=='table' then
+    local rval = {}
+    -- Consider arrays separately
+    local bArray, maxCount = isArray(v)
+    if bArray then
+      for i = 1,maxCount do
+        table.insert(rval, encode(v[i]))
+      end
+    else	-- An object, not an array
+      for i,j in base.pairs(v) do
+        if isEncodable(i) and isEncodable(j) then
+          table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
+        end
+      end
+    end
+    if bArray then
+      return '[' .. table.concat(rval,',') ..']'
+    else
+      return '{' .. table.concat(rval,',') .. '}'
+    end
+  end
+  
+  -- Handle null values
+  if vtype=='function' and v==null then
+    return 'null'
+  end
+  
+  base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
+end
+
+
+--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
+-- @param s The string to scan.
+-- @return Lua objectthat was scanned, as a Lua table / string / number / boolean or nil.
+function decode(s)
+	-- Function is re-defined below after token and other items are created.
+	-- Just defined here for code neatness.
+	return null
+end
+
+--- The null function allows one to specify a null value in an associative array (which is otherwise
+-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
+function null()
+  return null -- so json.null() will also return null ;-)
+end
+
+-----------------------------------------------------------------------------
+-- Internal, PRIVATE functions.
+-----------------------------------------------------------------------------
+
+--- Encodes a string to be JSON-compatible.
+-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
+-- @param s The string to return as a JSON encoded (i.e. backquoted string)
+-- @return The string appropriately escaped.
+local qrep = {["\\"]="\\\\", ['"']='\\"',['\n']='\\n',['\t']='\\t'}
+function encodeString(s)
+  return tostring(s):gsub('["\\\n\t]',qrep)
+end
+
+-- Determines whether the given Lua type is an array or a table / dictionary.
+-- We consider any table an array if it has indexes 1..n for its n items, and no
+-- other data in the table.
+-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
+-- @param t The table to evaluate as an array
+-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
+-- the second returned value is the maximum
+-- number of indexed elements in the array. 
+function isArray(t)
+  -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable 
+  -- (with the possible exception of 'n')
+  local maxIndex = 0
+  for k,v in base.pairs(t) do
+    if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then	-- k,v is an indexed pair
+      if (not isEncodable(v)) then return false end	-- All array elements must be encodable
+      maxIndex = math.max(maxIndex,k)
+    else
+      if (k=='n') then
+        if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
+      else -- Else of (k=='n')
+        if isEncodable(v) then return false end
+      end  -- End of (k~='n')
+    end -- End of k,v not an indexed pair
+  end  -- End of loop across all pairs
+  return true, maxIndex
+end
+
+--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
+-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
+-- In this implementation, all other types are ignored.
+-- @param o The object to examine.
+-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
+function isEncodable(o)
+  local t = base.type(o)
+  return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) 
+end
+
+-- Radical performance improvement for decode from Eike Decker!
+do
+	local type = base.type
+	local error = base.error
+	local assert = base.assert
+	local print = base.print
+	local tonumber = base.tonumber
+	-- initialize some values to be used in decoding function
+	
+	-- initializes a table to contain a byte=>table mapping
+	-- the table contains tokens (byte values) as keys and maps them on other
+	-- token tables (mostly, the boolean value 'true' is used to indicate termination
+	-- of a token sequence)
+	-- the token table's purpose is, that it allows scanning a sequence of bytes
+	-- until something interesting has been found (e.g. a token that is not expected)
+	-- name is a descriptor for the table to be printed in error messages
+	local function init_token_table (tt)
+		local struct = {}
+		local value
+		function struct:link(other_tt)
+			value = other_tt
+			return struct
+		end
+		function struct:to(chars)
+			for i=1,#chars do 
+				tt[chars:byte(i)] = value
+			end
+			return struct
+		end
+		return function (name)
+			tt.name = name
+			return struct
+		end
+	end
+	
+	-- keep "named" byte values at hands
+	local 
+		c_esc,
+		c_e,
+		c_l,
+		c_r,
+		c_u,
+		c_f,
+		c_a,
+		c_s,
+		c_slash = ("\\elrufas/"):byte(1,9)
+	
+	-- token tables - tt_doublequote_string = strDoubleQuot, tt_singlequote_string = strSingleQuot
+	local 
+		tt_object_key,
+		tt_object_colon,
+		tt_object_value,
+		tt_doublequote_string,
+		tt_singlequote_string,
+		tt_array_value,
+		tt_array_seperator,
+		tt_numeric,
+		tt_boolean,
+		tt_null,
+		tt_comment_start,
+		tt_comment_middle,
+		tt_ignore --< tt_ignore is special - marked tokens will be tt_ignored
+			= {},{},{},{},{},{},{},{},{},{},{},{},{}
+	
+	-- strings to be used in certain token tables
+	local strchars = "" -- all valid string characters (all except newlines)
+	local allchars = "" -- all characters that are valid in comments
+	--local escapechar = {}
+	for i=0,0xff do 
+		local c = string.char(i)
+		if c~="\n" and c~="\r" then strchars = strchars .. c end
+		allchars = allchars .. c
+		--escapechar[i] = "\\" .. string.char(i)
+	end
+	
+--[[	
+	charstounescape = "\"\'\\bfnrt/";
+	unescapechars = "\"'\\\b\f\n\r\t\/";
+	for i=1,#charstounescape do
+		escapechar[ charstounescape:byte(i) ] = unescapechars:sub(i,i)
+	end
+]]--
+
+	-- obj key reader, expects the end of the object or a quoted string as key
+	init_token_table (tt_object_key) "object (' or \" or } or , expected)" 
+		:link(tt_singlequote_string) :to "'"
+		:link(tt_doublequote_string) :to '"'
+		:link(true)                  :to "}"
+		:link(tt_object_key)         :to ","
+		:link(tt_comment_start)      :to "/"
+		:link(tt_ignore)             :to " \t\r\n"
+	
+	
+	-- after the key, a colon is expected (or comment)
+	init_token_table (tt_object_colon) "object (: expected)" 
+		:link(tt_object_value)       :to ":"  
+		:link(tt_comment_start)      :to "/" 
+		:link(tt_ignore)             :to" \t\r\n"
+		
+	-- as values, anything is possible, numbers, arrays, objects, boolean, null, strings
+	init_token_table (tt_object_value) "object ({ or [ or ' or \" or number or boolean or null expected)"
+		:link(tt_object_key)         :to "{" 
+		:link(tt_array_seperator)    :to "[" 
+		:link(tt_singlequote_string) :to "'" 
+		:link(tt_doublequote_string) :to '"' 
+		:link(tt_numeric)            :to "0123456789.-" 
+		:link(tt_boolean)            :to "tf" 
+		:link(tt_null)               :to "n" 
+		:link(tt_comment_start)      :to "/" 
+		:link(tt_ignore)             :to " \t\r\n"
+		
+	-- token tables for reading strings
+	init_token_table (tt_doublequote_string) "double quoted string"
+		:link(tt_ignore)             :to (strchars)
+		:link(c_esc)                 :to "\\"
+		:link(true)                  :to '"'
+		
+	init_token_table (tt_singlequote_string) "single quoted string"
+		:link(tt_ignore)             :to (strchars)
+		:link(c_esc)                 :to "\\" 
+		:link(true)                  :to "'"
+		
+	-- array reader that expects termination of the array or a comma that indicates the next value
+	init_token_table (tt_array_value) "array (, or ] expected)"
+		:link(tt_array_seperator)    :to "," 
+		:link(true)                  :to "]"
+		:link(tt_comment_start)      :to "/" 
+		:link(tt_ignore)             :to " \t\r\n"
+	
+	-- a value, pretty similar to tt_object_value
+	init_token_table (tt_array_seperator) "array ({ or [ or ' or \" or number or boolean or null expected)"
+		:link(tt_object_key)         :to "{" 
+		:link(tt_array_seperator)    :to "[" 
+		:link(tt_singlequote_string) :to "'" 
+		:link(tt_doublequote_string) :to '"'  
+		:link(tt_comment_start)      :to "/" 
+		:link(tt_numeric)            :to "0123456789.-" 
+		:link(tt_boolean)            :to "tf" 
+		:link(tt_null)               :to "n" 
+		:link(tt_ignore)             :to " \t\r\n"
+	
+	-- valid number tokens
+	init_token_table (tt_numeric) "number"
+		:link(tt_ignore)             :to "0123456789.-Ee"
+		
+	-- once a comment has been started with /, a * is expected
+	init_token_table (tt_comment_start) "comment start (* expected)"
+		:link(tt_comment_middle)     :to "*"
+		
+	-- now everything is allowed, watch out for * though. The next char is then checked manually
+	init_token_table (tt_comment_middle) "comment end"
+		:link(tt_ignore)             :to (allchars)
+		:link(true)                  :to "*"
+		
+	function decode (js_string)
+		local pos = 1 -- position in the string
+		
+		-- read the next byte value
+		local function next_byte () pos = pos + 1 return js_string:byte(pos-1) end
+		
+		-- in case of error, report the location using line numbers
+		local function location () 
+			local n = ("\n"):byte()
+			local line,lpos = 1,0
+			for i=1,pos do 
+				if js_string:byte(i) == n then
+					line,lpos = line + 1,1
+				else
+					lpos = lpos + 1
+				end
+			end
+			return "Line "..line.." character "..lpos
+		end
+		
+		-- debug func
+		--local function status (str)
+		--	print(str.." ("..s:sub(math.max(1,p-10),p+10)..")")
+		--end
+		
+		-- read the next token, according to the passed token table
+		local function next_token (tok)
+			while pos <= #js_string do
+				local b = js_string:byte(pos) 
+				local t = tok[b]
+				if not t then 
+					error("Unexpected character at "..location()..": "..
+						string.char(b).." ("..b..") when reading "..tok.name.."\nContext: \n"..
+						js_string:sub(math.max(1,pos-30),pos+30).."\n"..(" "):rep(pos+math.min(-1,30-pos)).."^")
+				end
+				pos = pos + 1
+				if t~=tt_ignore then return t end
+			end
+			error("unexpected termination of JSON while looking for "..tok.name)
+		end
+		
+		-- read a string, double and single quoted ones
+		local function read_string (tok)
+			local start = pos
+			--local returnString = {}
+			repeat
+				local t = next_token(tok)
+				if t == c_esc then 
+					--table.insert(returnString, js_string:sub(start, pos-2))
+					--table.insert(returnString, escapechar[ js_string:byte(pos) ])
+					pos = pos + 1
+					--start = pos
+				end -- jump over escaped chars, no matter what
+			until t == true
+			return (base.loadstring("return " .. js_string:sub(start-1, pos-1) ) ())
+
+			-- We consider the situation where no escaped chars were encountered separately,
+			-- and use the fastest possible return in this case.
+			
+			--if 0 == #returnString then
+			--	return js_string:sub(start,pos-2)
+			--else
+			--	table.insert(returnString, js_string:sub(start,pos-2))
+			--	return table.concat(returnString,"");
+			--end
+			--return js_string:sub(start,pos-2)
+		end
+		
+		local function read_num ()
+			local start = pos
+			while pos <= #js_string do
+				local b = js_string:byte(pos)
+				if not tt_numeric[b] then break end
+				pos = pos + 1
+			end
+			return tonumber(js_string:sub(start-1,pos-1))
+		end
+		
+		-- read_bool and read_null are both making an assumption that I have not tested:
+		-- I would expect that the string extraction is more expensive than actually 
+		-- making manual comparision of the byte values
+		local function read_bool () 
+			pos = pos + 3
+			local a,b,c,d = js_string:byte(pos-3,pos)
+			if a == c_r and b == c_u and c == c_e then return true end
+			pos = pos + 1
+			if a ~= c_a or b ~= c_l or c ~= c_s or d ~= c_e then 
+				error("Invalid boolean: "..js_string:sub(math.max(1,pos-5),pos+5)) 
+			end
+			return false
+		end
+		
+		-- same as read_bool: only last 
+		local function read_null ()
+			pos = pos + 3
+			local u,l1,l2 = js_string:byte(pos-3,pos-1)
+			if u == c_u and l1 == c_l and l2 == c_l then return nil end
+			error("Invalid value (expected null):"..js_string:sub(pos-4,pos-1)..
+				" ("..js_string:byte(pos-1).."="..js_string:sub(pos-1,pos-1).." / "..c_l..")")
+		end
+		
+		local read_object_value,read_object_key,read_array,read_value,read_comment
+	
+		-- read a value depending on what token was returned, might require info what was used (in case of comments)
+		function read_value (t,fromt)
+			if t == tt_object_key         then return read_object_key({}) end
+			if t == tt_array_seperator    then return read_array({}) end
+			if t == tt_singlequote_string or 
+			   t == tt_doublequote_string then return read_string(t) end
+			if t == tt_numeric            then return read_num() end
+			if t == tt_boolean            then return read_bool() end	
+			if t == tt_null               then return read_null() end
+			if t == tt_comment_start      then return read_value(read_comment(fromt)) end
+			error("unexpected termination - "..js_string:sub(math.max(1,pos-10),pos+10))
+		end
+		
+		-- read comments until something noncomment like surfaces, using the token reader which was 
+		-- used when stumbling over this comment
+		function read_comment (fromt)
+			while true do
+				next_token(tt_comment_start)
+				while true do
+					local t = next_token(tt_comment_middle)
+					if next_byte() == c_slash then
+						local t = next_token(fromt)
+						if t~= tt_comment_start then return t end
+						break
+					end
+				end
+			end
+		end
+		
+		-- read arrays, empty array expected as o arg
+		function read_array (o,i)
+			--if not i then status "arr open" end
+			i = i or 1
+			-- loop until ...
+			while true do
+				o[i] = read_value(next_token(tt_array_seperator),tt_array_seperator)
+				local t = next_token(tt_array_value)
+				if t == tt_comment_start then
+					t = read_comment(tt_array_value)
+				end
+				if t == true then  -- ... we found a terminator token
+					--status "arr close"
+					return o 
+				end
+				i = i + 1			
+			end
+		end
+		
+		-- object value reading
+		function read_object_value (o)
+			local t = next_token(tt_object_value)
+			return read_value(t,tt_object_value)
+		end
+		
+		-- object key reading, might also terminate the object
+		function read_object_key (o)
+			while true do
+				local t = next_token(tt_object_key)
+				if t == tt_comment_start then
+					t = read_comment(tt_object_key)
+				end
+				if t == true then return o end
+				if t == tt_object_key then return read_object_key(o) end
+				local k = read_string(t)
+				
+				if next_token(tt_object_colon) == tt_comment_start then
+					t = read_comment(tt_object_colon)
+				end
+				
+				local v = read_object_value(o)
+				o[k] = v
+			end
+		end
+		
+		-- now let's read data from our string and pretend it's an object value
+		local r = read_object_value()
+		if pos<=#js_string then
+			-- not sure about what to do with dangling characters
+			--error("Dangling characters in JSON code ("..location()..")")
+		end
+		
+		return r
+	end
+end
Index: /tags/0.1/sha1.lua
===================================================================
--- /tags/0.1/sha1.lua	(revision 3)
+++ /tags/0.1/sha1.lua	(revision 3)
@@ -0,0 +1,717 @@
+--
+-- SHA-1 secure hash computation, and HMAC-SHA1 signature computation,
+-- in pure Lua (tested on Lua 5.1)
+--
+-- Latest version always at:  http://regex.info/blog/lua/sha1
+--
+-- Copyright 2009 Jeffrey Friedl
+-- jfriedl@yahoo.com
+-- http://regex.info/blog/
+-- 
+--
+-- Version 1 [May 28, 2009]
+--
+--
+-- Lua is a pathetic, horrid, turd of a language. Not only doesn't it have
+-- bitwise integer operators like OR and AND, it doesn't even have integers
+-- (and those, relatively speaking, are its good points). Yet, this
+-- implements the SHA-1 digest hash in pure Lua. While coding it, I felt as
+-- if I were chiseling NAND gates out of rough blocks of silicon. Those not
+-- already familiar with this woeful language may, upon seeing this code,
+-- throw up in their own mouth.
+--
+-- It's not super fast.... a 10k-byte message takes about 2 seconds on a
+-- circa-2008 mid-level server, but it should be plenty adequate for short
+-- messages, such as is often needed during authentication handshaking.
+--
+-- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm
+--
+-- This file creates four entries in the global namespace:
+--
+--   local hash_as_hex   = sha1(message)            -- returns a hex string
+--   local hash_as_data  = sha1_binary(message)     -- returns raw bytes
+--
+--   local hmac_as_hex   = hmac_sha1(key, message)        -- hex string
+--   local hmac_as_data  = hmac_sha1_binary(key, message) -- raw bytes
+--
+-- Pass sha1() a string, and it returns a hash as a 40-character hex string.
+-- For example, the call
+--
+--   local hash = sha1 "http://regex.info/blog/"
+--
+-- puts the 40-character string
+--
+--   "7f103bf600de51dfe91062300c14738b32725db5"
+-- 
+-- into the variable 'hash'
+--
+-- Pass sha1_hmac() a key and a message, and it returns the signature as a
+-- 40-byte hex string.
+--
+--
+-- The two "_binary" versions do the same, but return the 20-byte string of raw data
+-- that the 40-byte hex strings represent.
+--
+
+------------------------------------------------------------------------------------------
+------------------------------------------------------------------------------------------
+
+
+module("sha1", package.seeall)
+
+
+--
+-- Return a W32 object for the number zero
+--
+local function ZERO()
+   return {
+      false, false, false, false,     false, false, false, false, 
+      false, false, false, false,     false, false, false, false, 
+      false, false, false, false,     false, false, false, false, 
+      false, false, false, false,     false, false, false, false, 
+   }
+end
+
+local hex_to_bits = {
+   ["0"] = { false, false, false, false },
+   ["1"] = { false, false, false, true  },
+   ["2"] = { false, false, true,  false },
+   ["3"] = { false, false, true,  true  },
+
+   ["4"] = { false, true,  false, false },
+   ["5"] = { false, true,  false, true  },
+   ["6"] = { false, true,  true,  false },
+   ["7"] = { false, true,  true,  true  },
+
+   ["8"] = { true,  false, false, false },
+   ["9"] = { true,  false, false, true  },
+   ["A"] = { true,  false, true,  false },
+   ["B"] = { true,  false, true,  true  },
+
+   ["C"] = { true,  true,  false, false },
+   ["D"] = { true,  true,  false, true  },
+   ["E"] = { true,  true,  true,  false },
+   ["F"] = { true,  true,  true,  true  },
+
+   ["a"] = { true,  false, true,  false },
+   ["b"] = { true,  false, true,  true  },
+   ["c"] = { true,  true,  false, false },
+   ["d"] = { true,  true,  false, true  },
+   ["e"] = { true,  true,  true,  false },
+   ["f"] = { true,  true,  true,  true  },
+}
+
+--
+-- Given a string of 8 hex digits, return a W32 object representing that number
+--
+local function from_hex(hex)
+
+   assert(type(hex) == 'string')
+   assert(hex:match('^[0123456789abcdefABCDEF]+$'))
+   assert(#hex == 8)
+
+   local W32 = { }
+
+   for letter in hex:gmatch('.') do
+      local b = hex_to_bits[letter]
+      assert(b)
+      table.insert(W32, 1, b[1])
+      table.insert(W32, 1, b[2])
+      table.insert(W32, 1, b[3])
+      table.insert(W32, 1, b[4])
+   end
+
+   return W32
+end
+
+local function COPY(old)
+   local W32 = { }
+   for k,v in pairs(old) do
+      W32[k] = v
+   end
+
+   return W32
+end
+
+local function ADD(first, ...)
+
+   local a = COPY(first)
+
+   local C, b, sum
+
+   for v = 1, select('#', ...) do
+      b = select(v, ...)
+      C = 0
+
+      for i = 1, #a do
+         sum = (a[i] and 1 or 0)
+             + (b[i] and 1 or 0)
+             + C
+
+         if sum == 0 then
+            a[i] = false
+            C    = 0
+         elseif sum == 1 then
+            a[i] = true
+            C    = 0
+         elseif sum == 2 then
+            a[i] = false
+            C    = 1
+         else
+            a[i] = true
+            C    = 1
+         end
+      end
+      -- we drop any ending carry
+
+   end
+
+   return a
+end
+
+local function XOR(first, ...)
+
+   local a = COPY(first)
+   local b
+   for v = 1, select('#', ...) do
+      b = select(v, ...)
+      for i = 1, #a do
+         a[i] = a[i] ~= b[i]
+      end
+   end
+
+   return a
+
+end
+
+local function AND(a, b)
+
+   local c = ZERO()
+
+   for i = 1, #a do
+      -- only need to set true bits; other bits remain false
+      if  a[i] and b[i] then
+         c[i] = true
+      end
+   end
+
+   return c
+end
+
+local function OR(a, b)
+
+   local c = ZERO()
+
+   for i = 1, #a do
+      -- only need to set true bits; other bits remain false
+      if  a[i] or b[i] then
+         c[i] = true
+      end
+   end
+
+   return c
+end
+
+local function OR3(a, b, c)
+
+   local d = ZERO()
+
+   for i = 1, #a do
+      -- only need to set true bits; other bits remain false
+      if a[i] or b[i] or c[i] then
+         d[i] = true
+      end
+   end
+
+   return d
+end
+
+local function NOT(a)
+
+   local b = ZERO()
+
+   for i = 1, #a do
+      -- only need to set true bits; other bits remain false
+      if not a[i] then
+         b[i] = true
+      end
+   end
+
+   return b
+end
+
+local function ROTATE(bits, a)
+
+   local b = COPY(a)
+
+   while bits > 0 do
+      bits = bits - 1
+      table.insert(b, 1, table.remove(b))
+   end
+
+   return b
+
+end
+
+
+local binary_to_hex = {
+   ["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",
+}
+
+local function asHEX(a)
+
+   local hex = ""
+   local i = 1
+   while i < #a do
+      local binary = (a[i + 3] and '1' or '0')
+                     ..
+                     (a[i + 2] and '1' or '0')
+                     ..
+                     (a[i + 1] and '1' or '0')
+                     ..
+                     (a[i + 0] and '1' or '0')
+
+      hex = binary_to_hex[binary] .. hex
+
+      i = i + 4
+   end
+
+   return hex
+
+end
+
+local x67452301 = from_hex("67452301")
+local xEFCDAB89 = from_hex("EFCDAB89")
+local x98BADCFE = from_hex("98BADCFE")
+local x10325476 = from_hex("10325476")
+local xC3D2E1F0 = from_hex("C3D2E1F0")
+
+local x5A827999 = from_hex("5A827999")
+local x6ED9EBA1 = from_hex("6ED9EBA1")
+local x8F1BBCDC = from_hex("8F1BBCDC")
+local xCA62C1D6 = from_hex("CA62C1D6")
+
+
+local function sha1(msg)
+
+   assert(type(msg) == 'string')
+   assert(#msg < 0x7FFFFFFF) -- have no idea what would happen if it were large
+
+   local H0 = x67452301
+   local H1 = xEFCDAB89
+   local H2 = x98BADCFE
+   local H3 = x10325476
+   local H4 = xC3D2E1F0
+
+   local msg_len_in_bits = #msg * 8
+
+   local first_append = string.char(0x80) -- append a '1' bit plus seven '0' bits
+
+   local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
+   local current_mod = non_zero_message_bytes % 64
+   local second_append = ""
+   if current_mod ~= 0 then
+      second_append = string.rep(string.char(0), 64 - current_mod)
+   end
+
+   -- now to append the length as a 64-bit number.
+   local B1, R1 = math.modf(msg_len_in_bits  / 0x01000000)
+   local B2, R2 = math.modf( 0x01000000 * R1 / 0x00010000)
+   local B3, R3 = math.modf( 0x00010000 * R2 / 0x00000100)
+   local B4     =            0x00000100 * R3
+
+   local L64 = string.char( 0) .. string.char( 0) .. string.char( 0) .. string.char( 0) -- high 32 bits
+            .. string.char(B1) .. string.char(B2) .. string.char(B3) .. string.char(B4) --  low 32 bits
+
+
+
+   msg = msg .. first_append .. second_append .. L64         
+
+   assert(#msg % 64 == 0)
+
+   --local fd = io.open("/tmp/msg", "wb")
+   --fd:write(msg)
+   --fd:close()
+
+   local chunks = #msg / 64
+
+   local W = { }
+   local start, A, B, C, D, E, f, K, TEMP
+   local chunk = 0
+
+   while chunk < chunks do
+      --
+      -- break chunk up into W[0] through W[15]
+      --
+      start = chunk * 64 + 1
+      chunk = chunk + 1
+
+      for t = 0, 15 do
+         W[t] = from_hex(string.format("%02x%02x%02x%02x", msg:byte(start, start + 3)))
+         start = start + 4
+      end
+
+      --
+      -- build W[16] through W[79]
+      --
+      for t = 16, 79 do
+         -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). 
+         W[t] = ROTATE(1, XOR(W[t-3], W[t-8], W[t-14], W[t-16]))
+      end
+
+      A = H0
+      B = H1
+      C = H2
+      D = H3
+      E = H4
+
+      for t = 0, 79 do
+         if t <= 19 then
+            -- (B AND C) OR ((NOT B) AND D)
+            f = OR(AND(B, C), AND(NOT(B), D))
+            K = x5A827999
+         elseif t <= 39 then
+            -- B XOR C XOR D
+            f = XOR(B, C, D)
+            K = x6ED9EBA1
+         elseif t <= 59 then
+            -- (B AND C) OR (B AND D) OR (C AND D
+            f = OR3(AND(B, C), AND(B, D), AND(C, D))
+            K = x8F1BBCDC
+         else
+            -- B XOR C XOR D
+            f = XOR(B, C, D)
+            K = xCA62C1D6
+         end
+
+         -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 
+         TEMP = ADD(ROTATE(5, A), f, E, W[t], K)
+
+         --E = D;   D = C;    C = S30(B);   B = A;   A = TEMP;
+         E = D
+         D = C
+         C = ROTATE(30, B)
+         B = A
+         A = TEMP
+
+         --printf("t = %2d: %s  %s  %s  %s  %s", t, A:HEX(), B:HEX(), C:HEX(), D:HEX(), E:HEX())
+      end
+
+      -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 
+      H0 = ADD(H0, A)
+      H1 = ADD(H1, B)
+      H2 = ADD(H2, C)
+      H3 = ADD(H3, D)
+      H4 = ADD(H4, E)
+   end
+
+   return asHEX(H0) .. asHEX(H1) .. asHEX(H2) .. asHEX(H3) .. asHEX(H4)
+end
+
+local function hex_to_binary(hex)
+   return hex:gsub('..', function(hexval)
+                            return string.char(tonumber(hexval, 16))
+                         end)
+end
+
+local function sha1_binary(msg)
+   return hex_to_binary(sha1(msg))
+end
+
+local xor_with_0x5c = {
+   [string.char(  0)] = string.char( 92),   [string.char(  1)] = string.char( 93),
+   [string.char(  2)] = string.char( 94),   [string.char(  3)] = string.char( 95),
+   [string.char(  4)] = string.char( 88),   [string.char(  5)] = string.char( 89),
+   [string.char(  6)] = string.char( 90),   [string.char(  7)] = string.char( 91),
+   [string.char(  8)] = string.char( 84),   [string.char(  9)] = string.char( 85),
+   [string.char( 10)] = string.char( 86),   [string.char( 11)] = string.char( 87),
+   [string.char( 12)] = string.char( 80),   [string.char( 13)] = string.char( 81),
+   [string.char( 14)] = string.char( 82),   [string.char( 15)] = string.char( 83),
+   [string.char( 16)] = string.char( 76),   [string.char( 17)] = string.char( 77),
+   [string.char( 18)] = string.char( 78),   [string.char( 19)] = string.char( 79),
+   [string.char( 20)] = string.char( 72),   [string.char( 21)] = string.char( 73),
+   [string.char( 22)] = string.char( 74),   [string.char( 23)] = string.char( 75),
+   [string.char( 24)] = string.char( 68),   [string.char( 25)] = string.char( 69),
+   [string.char( 26)] = string.char( 70),   [string.char( 27)] = string.char( 71),
+   [string.char( 28)] = string.char( 64),   [string.char( 29)] = string.char( 65),
+   [string.char( 30)] = string.char( 66),   [string.char( 31)] = string.char( 67),
+   [string.char( 32)] = string.char(124),   [string.char( 33)] = string.char(125),
+   [string.char( 34)] = string.char(126),   [string.char( 35)] = string.char(127),
+   [string.char( 36)] = string.char(120),   [string.char( 37)] = string.char(121),
+   [string.char( 38)] = string.char(122),   [string.char( 39)] = string.char(123),
+   [string.char( 40)] = string.char(116),   [string.char( 41)] = string.char(117),
+   [string.char( 42)] = string.char(118),   [string.char( 43)] = string.char(119),
+   [string.char( 44)] = string.char(112),   [string.char( 45)] = string.char(113),
+   [string.char( 46)] = string.char(114),   [string.char( 47)] = string.char(115),
+   [string.char( 48)] = string.char(108),   [string.char( 49)] = string.char(109),
+   [string.char( 50)] = string.char(110),   [string.char( 51)] = string.char(111),
+   [string.char( 52)] = string.char(104),   [string.char( 53)] = string.char(105),
+   [string.char( 54)] = string.char(106),   [string.char( 55)] = string.char(107),
+   [string.char( 56)] = string.char(100),   [string.char( 57)] = string.char(101),
+   [string.char( 58)] = string.char(102),   [string.char( 59)] = string.char(103),
+   [string.char( 60)] = string.char( 96),   [string.char( 61)] = string.char( 97),
+   [string.char( 62)] = string.char( 98),   [string.char( 63)] = string.char( 99),
+   [string.char( 64)] = string.char( 28),   [string.char( 65)] = string.char( 29),
+   [string.char( 66)] = string.char( 30),   [string.char( 67)] = string.char( 31),
+   [string.char( 68)] = string.char( 24),   [string.char( 69)] = string.char( 25),
+   [string.char( 70)] = string.char( 26),   [string.char( 71)] = string.char( 27),
+   [string.char( 72)] = string.char( 20),   [string.char( 73)] = string.char( 21),
+   [string.char( 74)] = string.char( 22),   [string.char( 75)] = string.char( 23),
+   [string.char( 76)] = string.char( 16),   [string.char( 77)] = string.char( 17),
+   [string.char( 78)] = string.char( 18),   [string.char( 79)] = string.char( 19),
+   [string.char( 80)] = string.char( 12),   [string.char( 81)] = string.char( 13),
+   [string.char( 82)] = string.char( 14),   [string.char( 83)] = string.char( 15),
+   [string.char( 84)] = string.char(  8),   [string.char( 85)] = string.char(  9),
+   [string.char( 86)] = string.char( 10),   [string.char( 87)] = string.char( 11),
+   [string.char( 88)] = string.char(  4),   [string.char( 89)] = string.char(  5),
+   [string.char( 90)] = string.char(  6),   [string.char( 91)] = string.char(  7),
+   [string.char( 92)] = string.char(  0),   [string.char( 93)] = string.char(  1),
+   [string.char( 94)] = string.char(  2),   [string.char( 95)] = string.char(  3),
+   [string.char( 96)] = string.char( 60),   [string.char( 97)] = string.char( 61),
+   [string.char( 98)] = string.char( 62),   [string.char( 99)] = string.char( 63),
+   [string.char(100)] = string.char( 56),   [string.char(101)] = string.char( 57),
+   [string.char(102)] = string.char( 58),   [string.char(103)] = string.char( 59),
+   [string.char(104)] = string.char( 52),   [string.char(105)] = string.char( 53),
+   [string.char(106)] = string.char( 54),   [string.char(107)] = string.char( 55),
+   [string.char(108)] = string.char( 48),   [string.char(109)] = string.char( 49),
+   [string.char(110)] = string.char( 50),   [string.char(111)] = string.char( 51),
+   [string.char(112)] = string.char( 44),   [string.char(113)] = string.char( 45),
+   [string.char(114)] = string.char( 46),   [string.char(115)] = string.char( 47),
+   [string.char(116)] = string.char( 40),   [string.char(117)] = string.char( 41),
+   [string.char(118)] = string.char( 42),   [string.char(119)] = string.char( 43),
+   [string.char(120)] = string.char( 36),   [string.char(121)] = string.char( 37),
+   [string.char(122)] = string.char( 38),   [string.char(123)] = string.char( 39),
+   [string.char(124)] = string.char( 32),   [string.char(125)] = string.char( 33),
+   [string.char(126)] = string.char( 34),   [string.char(127)] = string.char( 35),
+   [string.char(128)] = string.char(220),   [string.char(129)] = string.char(221),
+   [string.char(130)] = string.char(222),   [string.char(131)] = string.char(223),
+   [string.char(132)] = string.char(216),   [string.char(133)] = string.char(217),
+   [string.char(134)] = string.char(218),   [string.char(135)] = string.char(219),
+   [string.char(136)] = string.char(212),   [string.char(137)] = string.char(213),
+   [string.char(138)] = string.char(214),   [string.char(139)] = string.char(215),
+   [string.char(140)] = string.char(208),   [string.char(141)] = string.char(209),
+   [string.char(142)] = string.char(210),   [string.char(143)] = string.char(211),
+   [string.char(144)] = string.char(204),   [string.char(145)] = string.char(205),
+   [string.char(146)] = string.char(206),   [string.char(147)] = string.char(207),
+   [string.char(148)] = string.char(200),   [string.char(149)] = string.char(201),
+   [string.char(150)] = string.char(202),   [string.char(151)] = string.char(203),
+   [string.char(152)] = string.char(196),   [string.char(153)] = string.char(197),
+   [string.char(154)] = string.char(198),   [string.char(155)] = string.char(199),
+   [string.char(156)] = string.char(192),   [string.char(157)] = string.char(193),
+   [string.char(158)] = string.char(194),   [string.char(159)] = string.char(195),
+   [string.char(160)] = string.char(252),   [string.char(161)] = string.char(253),
+   [string.char(162)] = string.char(254),   [string.char(163)] = string.char(255),
+   [string.char(164)] = string.char(248),   [string.char(165)] = string.char(249),
+   [string.char(166)] = string.char(250),   [string.char(167)] = string.char(251),
+   [string.char(168)] = string.char(244),   [string.char(169)] = string.char(245),
+   [string.char(170)] = string.char(246),   [string.char(171)] = string.char(247),
+   [string.char(172)] = string.char(240),   [string.char(173)] = string.char(241),
+   [string.char(174)] = string.char(242),   [string.char(175)] = string.char(243),
+   [string.char(176)] = string.char(236),   [string.char(177)] = string.char(237),
+   [string.char(178)] = string.char(238),   [string.char(179)] = string.char(239),
+   [string.char(180)] = string.char(232),   [string.char(181)] = string.char(233),
+   [string.char(182)] = string.char(234),   [string.char(183)] = string.char(235),
+   [string.char(184)] = string.char(228),   [string.char(185)] = string.char(229),
+   [string.char(186)] = string.char(230),   [string.char(187)] = string.char(231),
+   [string.char(188)] = string.char(224),   [string.char(189)] = string.char(225),
+   [string.char(190)] = string.char(226),   [string.char(191)] = string.char(227),
+   [string.char(192)] = string.char(156),   [string.char(193)] = string.char(157),
+   [string.char(194)] = string.char(158),   [string.char(195)] = string.char(159),
+   [string.char(196)] = string.char(152),   [string.char(197)] = string.char(153),
+   [string.char(198)] = string.char(154),   [string.char(199)] = string.char(155),
+   [string.char(200)] = string.char(148),   [string.char(201)] = string.char(149),
+   [string.char(202)] = string.char(150),   [string.char(203)] = string.char(151),
+   [string.char(204)] = string.char(144),   [string.char(205)] = string.char(145),
+   [string.char(206)] = string.char(146),   [string.char(207)] = string.char(147),
+   [string.char(208)] = string.char(140),   [string.char(209)] = string.char(141),
+   [string.char(210)] = string.char(142),   [string.char(211)] = string.char(143),
+   [string.char(212)] = string.char(136),   [string.char(213)] = string.char(137),
+   [string.char(214)] = string.char(138),   [string.char(215)] = string.char(139),
+   [string.char(216)] = string.char(132),   [string.char(217)] = string.char(133),
+   [string.char(218)] = string.char(134),   [string.char(219)] = string.char(135),
+   [string.char(220)] = string.char(128),   [string.char(221)] = string.char(129),
+   [string.char(222)] = string.char(130),   [string.char(223)] = string.char(131),
+   [string.char(224)] = string.char(188),   [string.char(225)] = string.char(189),
+   [string.char(226)] = string.char(190),   [string.char(227)] = string.char(191),
+   [string.char(228)] = string.char(184),   [string.char(229)] = string.char(185),
+   [string.char(230)] = string.char(186),   [string.char(231)] = string.char(187),
+   [string.char(232)] = string.char(180),   [string.char(233)] = string.char(181),
+   [string.char(234)] = string.char(182),   [string.char(235)] = string.char(183),
+   [string.char(236)] = string.char(176),   [string.char(237)] = string.char(177),
+   [string.char(238)] = string.char(178),   [string.char(239)] = string.char(179),
+   [string.char(240)] = string.char(172),   [string.char(241)] = string.char(173),
+   [string.char(242)] = string.char(174),   [string.char(243)] = string.char(175),
+   [string.char(244)] = string.char(168),   [string.char(245)] = string.char(169),
+   [string.char(246)] = string.char(170),   [string.char(247)] = string.char(171),
+   [string.char(248)] = string.char(164),   [string.char(249)] = string.char(165),
+   [string.char(250)] = string.char(166),   [string.char(251)] = string.char(167),
+   [string.char(252)] = string.char(160),   [string.char(253)] = string.char(161),
+   [string.char(254)] = string.char(162),   [string.char(255)] = string.char(163),
+}
+
+local xor_with_0x36 = {
+   [string.char(  0)] = string.char( 54),   [string.char(  1)] = string.char( 55),
+   [string.char(  2)] = string.char( 52),   [string.char(  3)] = string.char( 53),
+   [string.char(  4)] = string.char( 50),   [string.char(  5)] = string.char( 51),
+   [string.char(  6)] = string.char( 48),   [string.char(  7)] = string.char( 49),
+   [string.char(  8)] = string.char( 62),   [string.char(  9)] = string.char( 63),
+   [string.char( 10)] = string.char( 60),   [string.char( 11)] = string.char( 61),
+   [string.char( 12)] = string.char( 58),   [string.char( 13)] = string.char( 59),
+   [string.char( 14)] = string.char( 56),   [string.char( 15)] = string.char( 57),
+   [string.char( 16)] = string.char( 38),   [string.char( 17)] = string.char( 39),
+   [string.char( 18)] = string.char( 36),   [string.char( 19)] = string.char( 37),
+   [string.char( 20)] = string.char( 34),   [string.char( 21)] = string.char( 35),
+   [string.char( 22)] = string.char( 32),   [string.char( 23)] = string.char( 33),
+   [string.char( 24)] = string.char( 46),   [string.char( 25)] = string.char( 47),
+   [string.char( 26)] = string.char( 44),   [string.char( 27)] = string.char( 45),
+   [string.char( 28)] = string.char( 42),   [string.char( 29)] = string.char( 43),
+   [string.char( 30)] = string.char( 40),   [string.char( 31)] = string.char( 41),
+   [string.char( 32)] = string.char( 22),   [string.char( 33)] = string.char( 23),
+   [string.char( 34)] = string.char( 20),   [string.char( 35)] = string.char( 21),
+   [string.char( 36)] = string.char( 18),   [string.char( 37)] = string.char( 19),
+   [string.char( 38)] = string.char( 16),   [string.char( 39)] = string.char( 17),
+   [string.char( 40)] = string.char( 30),   [string.char( 41)] = string.char( 31),
+   [string.char( 42)] = string.char( 28),   [string.char( 43)] = string.char( 29),
+   [string.char( 44)] = string.char( 26),   [string.char( 45)] = string.char( 27),
+   [string.char( 46)] = string.char( 24),   [string.char( 47)] = string.char( 25),
+   [string.char( 48)] = string.char(  6),   [string.char( 49)] = string.char(  7),
+   [string.char( 50)] = string.char(  4),   [string.char( 51)] = string.char(  5),
+   [string.char( 52)] = string.char(  2),   [string.char( 53)] = string.char(  3),
+   [string.char( 54)] = string.char(  0),   [string.char( 55)] = string.char(  1),
+   [string.char( 56)] = string.char( 14),   [string.char( 57)] = string.char( 15),
+   [string.char( 58)] = string.char( 12),   [string.char( 59)] = string.char( 13),
+   [string.char( 60)] = string.char( 10),   [string.char( 61)] = string.char( 11),
+   [string.char( 62)] = string.char(  8),   [string.char( 63)] = string.char(  9),
+   [string.char( 64)] = string.char(118),   [string.char( 65)] = string.char(119),
+   [string.char( 66)] = string.char(116),   [string.char( 67)] = string.char(117),
+   [string.char( 68)] = string.char(114),   [string.char( 69)] = string.char(115),
+   [string.char( 70)] = string.char(112),   [string.char( 71)] = string.char(113),
+   [string.char( 72)] = string.char(126),   [string.char( 73)] = string.char(127),
+   [string.char( 74)] = string.char(124),   [string.char( 75)] = string.char(125),
+   [string.char( 76)] = string.char(122),   [string.char( 77)] = string.char(123),
+   [string.char( 78)] = string.char(120),   [string.char( 79)] = string.char(121),
+   [string.char( 80)] = string.char(102),   [string.char( 81)] = string.char(103),
+   [string.char( 82)] = string.char(100),   [string.char( 83)] = string.char(101),
+   [string.char( 84)] = string.char( 98),   [string.char( 85)] = string.char( 99),
+   [string.char( 86)] = string.char( 96),   [string.char( 87)] = string.char( 97),
+   [string.char( 88)] = string.char(110),   [string.char( 89)] = string.char(111),
+   [string.char( 90)] = string.char(108),   [string.char( 91)] = string.char(109),
+   [string.char( 92)] = string.char(106),   [string.char( 93)] = string.char(107),
+   [string.char( 94)] = string.char(104),   [string.char( 95)] = string.char(105),
+   [string.char( 96)] = string.char( 86),   [string.char( 97)] = string.char( 87),
+   [string.char( 98)] = string.char( 84),   [string.char( 99)] = string.char( 85),
+   [string.char(100)] = string.char( 82),   [string.char(101)] = string.char( 83),
+   [string.char(102)] = string.char( 80),   [string.char(103)] = string.char( 81),
+   [string.char(104)] = string.char( 94),   [string.char(105)] = string.char( 95),
+   [string.char(106)] = string.char( 92),   [string.char(107)] = string.char( 93),
+   [string.char(108)] = string.char( 90),   [string.char(109)] = string.char( 91),
+   [string.char(110)] = string.char( 88),   [string.char(111)] = string.char( 89),
+   [string.char(112)] = string.char( 70),   [string.char(113)] = string.char( 71),
+   [string.char(114)] = string.char( 68),   [string.char(115)] = string.char( 69),
+   [string.char(116)] = string.char( 66),   [string.char(117)] = string.char( 67),
+   [string.char(118)] = string.char( 64),   [string.char(119)] = string.char( 65),
+   [string.char(120)] = string.char( 78),   [string.char(121)] = string.char( 79),
+   [string.char(122)] = string.char( 76),   [string.char(123)] = string.char( 77),
+   [string.char(124)] = string.char( 74),   [string.char(125)] = string.char( 75),
+   [string.char(126)] = string.char( 72),   [string.char(127)] = string.char( 73),
+   [string.char(128)] = string.char(182),   [string.char(129)] = string.char(183),
+   [string.char(130)] = string.char(180),   [string.char(131)] = string.char(181),
+   [string.char(132)] = string.char(178),   [string.char(133)] = string.char(179),
+   [string.char(134)] = string.char(176),   [string.char(135)] = string.char(177),
+   [string.char(136)] = string.char(190),   [string.char(137)] = string.char(191),
+   [string.char(138)] = string.char(188),   [string.char(139)] = string.char(189),
+   [string.char(140)] = string.char(186),   [string.char(141)] = string.char(187),
+   [string.char(142)] = string.char(184),   [string.char(143)] = string.char(185),
+   [string.char(144)] = string.char(166),   [string.char(145)] = string.char(167),
+   [string.char(146)] = string.char(164),   [string.char(147)] = string.char(165),
+   [string.char(148)] = string.char(162),   [string.char(149)] = string.char(163),
+   [string.char(150)] = string.char(160),   [string.char(151)] = string.char(161),
+   [string.char(152)] = string.char(174),   [string.char(153)] = string.char(175),
+   [string.char(154)] = string.char(172),   [string.char(155)] = string.char(173),
+   [string.char(156)] = string.char(170),   [string.char(157)] = string.char(171),
+   [string.char(158)] = string.char(168),   [string.char(159)] = string.char(169),
+   [string.char(160)] = string.char(150),   [string.char(161)] = string.char(151),
+   [string.char(162)] = string.char(148),   [string.char(163)] = string.char(149),
+   [string.char(164)] = string.char(146),   [string.char(165)] = string.char(147),
+   [string.char(166)] = string.char(144),   [string.char(167)] = string.char(145),
+   [string.char(168)] = string.char(158),   [string.char(169)] = string.char(159),
+   [string.char(170)] = string.char(156),   [string.char(171)] = string.char(157),
+   [string.char(172)] = string.char(154),   [string.char(173)] = string.char(155),
+   [string.char(174)] = string.char(152),   [string.char(175)] = string.char(153),
+   [string.char(176)] = string.char(134),   [string.char(177)] = string.char(135),
+   [string.char(178)] = string.char(132),   [string.char(179)] = string.char(133),
+   [string.char(180)] = string.char(130),   [string.char(181)] = string.char(131),
+   [string.char(182)] = string.char(128),   [string.char(183)] = string.char(129),
+   [string.char(184)] = string.char(142),   [string.char(185)] = string.char(143),
+   [string.char(186)] = string.char(140),   [string.char(187)] = string.char(141),
+   [string.char(188)] = string.char(138),   [string.char(189)] = string.char(139),
+   [string.char(190)] = string.char(136),   [string.char(191)] = string.char(137),
+   [string.char(192)] = string.char(246),   [string.char(193)] = string.char(247),
+   [string.char(194)] = string.char(244),   [string.char(195)] = string.char(245),
+   [string.char(196)] = string.char(242),   [string.char(197)] = string.char(243),
+   [string.char(198)] = string.char(240),   [string.char(199)] = string.char(241),
+   [string.char(200)] = string.char(254),   [string.char(201)] = string.char(255),
+   [string.char(202)] = string.char(252),   [string.char(203)] = string.char(253),
+   [string.char(204)] = string.char(250),   [string.char(205)] = string.char(251),
+   [string.char(206)] = string.char(248),   [string.char(207)] = string.char(249),
+   [string.char(208)] = string.char(230),   [string.char(209)] = string.char(231),
+   [string.char(210)] = string.char(228),   [string.char(211)] = string.char(229),
+   [string.char(212)] = string.char(226),   [string.char(213)] = string.char(227),
+   [string.char(214)] = string.char(224),   [string.char(215)] = string.char(225),
+   [string.char(216)] = string.char(238),   [string.char(217)] = string.char(239),
+   [string.char(218)] = string.char(236),   [string.char(219)] = string.char(237),
+   [string.char(220)] = string.char(234),   [string.char(221)] = string.char(235),
+   [string.char(222)] = string.char(232),   [string.char(223)] = string.char(233),
+   [string.char(224)] = string.char(214),   [string.char(225)] = string.char(215),
+   [string.char(226)] = string.char(212),   [string.char(227)] = string.char(213),
+   [string.char(228)] = string.char(210),   [string.char(229)] = string.char(211),
+   [string.char(230)] = string.char(208),   [string.char(231)] = string.char(209),
+   [string.char(232)] = string.char(222),   [string.char(233)] = string.char(223),
+   [string.char(234)] = string.char(220),   [string.char(235)] = string.char(221),
+   [string.char(236)] = string.char(218),   [string.char(237)] = string.char(219),
+   [string.char(238)] = string.char(216),   [string.char(239)] = string.char(217),
+   [string.char(240)] = string.char(198),   [string.char(241)] = string.char(199),
+   [string.char(242)] = string.char(196),   [string.char(243)] = string.char(197),
+   [string.char(244)] = string.char(194),   [string.char(245)] = string.char(195),
+   [string.char(246)] = string.char(192),   [string.char(247)] = string.char(193),
+   [string.char(248)] = string.char(206),   [string.char(249)] = string.char(207),
+   [string.char(250)] = string.char(204),   [string.char(251)] = string.char(205),
+   [string.char(252)] = string.char(202),   [string.char(253)] = string.char(203),
+   [string.char(254)] = string.char(200),   [string.char(255)] = string.char(201),
+}
+
+
+local blocksize = 64 -- 512 bits
+
+function hmac_sha1(key, text)
+   assert(type(key)  == 'string', "key passed to hmac_sha1 should be a string")
+   assert(type(text) == 'string', "text passed to hmac_sha1 should be a string")
+
+   if #key > blocksize then
+      key = sha1_binary(key)
+   end
+
+   local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key)
+   local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key)
+
+   return sha1(key_xord_with_0x5c .. sha1_binary(key_xord_with_0x36 .. text))
+end
+
+function hmac_sha1_binary(key, text)
+   return hex_to_binary(hmac_sha1(key, text))
+end
Index: /tags/0.1/S_Freebox.xml
===================================================================
--- /tags/0.1/S_Freebox.xml	(revision 3)
+++ /tags/0.1/S_Freebox.xml	(revision 3)
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable>
+    	<name>StatusAuthentification</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>StatusConfigFreebox</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>FrequenceMiseAJour</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>WifiActif</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>FtpActif</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>PartageFichiersWinActif</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>PartageImprimantesWinActif</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_frequence</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+  <action>
+  	<name>Authentification</name>
+  </action>
+  <action>
+    <name>ModifierFrequenceMiseAJour</name>
+    <argumentList>
+      <argument>
+        <name>Frequence</name>
+        <direction>in</direction>
+        <relatedStateVariable>A_ARG_TYPE_frequence</relatedStateVariable>
+      </argument>
+    </argumentList>
+  </action>
+  <action>
+  	<name>ActiverWifi</name>
+  </action>
+  <action>
+  	<name>DesactiverWifi</name>
+  </action>
+  <action>
+  	<name>ActiverFtp</name>
+  </action>
+  <action>
+  	<name>DesactiverFtp</name>
+  </action>
+  <action>
+  	<name>ActiverPartageFichiersWin</name>
+  </action>
+  <action>
+  	<name>DesactiverPartageFichiersWin</name>
+  </action>
+  <action>
+  	<name>ActiverPartageImprimantesWin</name>
+  </action>
+  <action>
+  	<name>DesactiverPartageImprimantesWin</name>
+  </action>
+  </actionList>
+</scpd>
Index: /tags/0.1/D_Freebox.xml
===================================================================
--- /tags/0.1/D_Freebox.xml	(revision 3)
+++ /tags/0.1/D_Freebox.xml	(revision 3)
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <deviceType>urn:freebox-fr:device:Freebox:1</deviceType>
+    <staticJson>D_Freebox.json</staticJson>
+    <friendlyName>Freebox Revolution</friendlyName>
+    <manufacturer>Free</manufacturer>
+    <manufacturerURL>http://www.free.fr</manufacturerURL>
+    <modelDescription>Freebox Revolution</modelDescription>
+    <modelName>Freebox Revolution</modelName>
+    <modelNumber>6</modelNumber>
+    <handleChildren>0</handleChildren>
+    <implementationList>
+      <implementationFile>I_Freebox.xml</implementationFile>
+    </implementationList>
+    <serviceList>
+      <service>
+        <serviceType>urn:freebox-fr:service:Freebox:1</serviceType>
+        <serviceId>urn:freebox-fr:serviceId:Freebox1</serviceId>
+        <SCPDURL>S_Freebox.xml</SCPDURL>
+      </service>
+    </serviceList>
+  </device>
+</root>
Index: /trunk/J_Freebox.js
===================================================================
--- /trunk/J_Freebox.js	(revision 3)
+++ /trunk/J_Freebox.js	(revision 3)
@@ -0,0 +1,139 @@
+var timeoutVar = undefined;
+var browserIE = false
+var parseXml = undefined;
+
+function showNetDevices (device)
+{
+	if (typeof timeoutVar != 'undefined') {
+		clearTimeout(timeoutVar);
+	}
+
+	detectBrowser();
+
+	initXMLParser();
+
+	var html = '';
+
+	html += '<table border="1">';
+	html += '<tr align="center" bgcolor="LightBlue">';
+	html += '<th>Nom</td>';
+	html += '<th>IP</td>';
+	html += '<th>MAC</td>';
+	html += '<th>Joignable</td>';
+	html += '</tr>';
+	html += '<tr>';
+
+	var xmlnetDevices
+	var netDevices = get_device_state(device, "urn:freebox-fr:serviceId:Freebox1", "PeripheriquesReseau", 1);
+	if (netDevices != undefined && netDevices != "") {
+		xmlnetDevices = parseXml(netDevices);
+	}
+	if (typeof xmlnetDevices != 'undefined') {
+		var device = xmlnetDevices.getElementsByTagName("device");
+		if (device.length > 0) {
+			for (i=0; i<device.length; i++) {
+				var name = extractXmlTag(device[i], 'name');
+				var mac = extractXmlTag(device[i], 'mac');
+				var ip = extractXmlTag(device[i], 'ip');
+				var reachable = extractXmlTag(device[i], 'reachable');
+				if (reachable == 'true') {
+					html += '<tr align="center">';
+				}
+				else {
+					html += '<tr align="center" bgcolor="LightGrey">';
+				}
+				html += '<td>' + name + '</td>';
+				html += '<td>' + ip + '</td>';
+				html += '<td>' + mac + '</td>';
+				if (reachable == 'true') {
+					html += '<td>oui</td>';
+				}
+				else {
+					html += '<td>non</td>';
+				}
+				html += '</tr>';
+			}
+		}
+	}
+	html += '</table>';
+
+	set_panel_html(html);
+}
+
+function detectBrowser()
+{
+	var place = navigator.userAgent.toLowerCase().indexOf('msie');
+	if (place == -1) {
+		browserIE = false;
+	}
+	else {
+		browserIE = true;
+	}
+}
+
+function initXMLParser()
+{
+	if (typeof parseXml == 'undefined') {
+		if (typeof window.DOMParser != "undefined") {
+			parseXml = function(xmlStr) {
+				return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
+			};
+		}
+		else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
+			parseXml = function(xmlStr) {
+				var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
+				xmlDoc.async = "false";
+				xmlDoc.loadXML(xmlStr);
+				return xmlDoc;
+			};
+		}
+		else {
+			parseXml = function(xmlStr) {
+				return undefined;
+			};
+		}
+	}
+}
+
+function extractXmlTag(parent, tag)
+{
+	var value = undefined;
+	for (j=0; j<parent.childNodes.length; j++) {
+		if (parent.childNodes[j].tagName == tag) {
+			value = parent.childNodes[j].textContent;
+			break;
+		}
+	}
+	return value;
+}
+
+function call_action (device, sid, actname, args) {
+	var q={
+		'id':'lu_action',
+		'output_format':'xml',
+		'DeviceNum':device,
+		'serviceId':sid,
+		'action':actname
+	};
+	var key, count = 0;
+	for(key in args) {
+		count++;
+	}
+	if (count > 0) {
+		Object.extend(q, args);
+	}
+    if (browserIE) {
+    	Object.extend(q, {timestamp: new Date().getTime()}); //we need this to avoid IE caching of the AJAX get
+    }
+	new Ajax.Request (command_url+'/data_request', {
+		method: 'get',
+		parameters: q,
+		onSuccess: function (response) {
+		},
+		onFailure: function (response) {
+		},
+		onComplete: function (response) {
+		}
+	});
+}
+
Index: /trunk/L_Freebox.lua
===================================================================
--- /trunk/L_Freebox.lua	(revision 1)
+++ /trunk/L_Freebox.lua	(revision 3)
@@ -9,5 +9,5 @@
 
 
-local PLUGIN_VERSION = "0.1"
+local PLUGIN_VERSION = "0.2"
 
 local THIS_DEVICE = 0
@@ -21,4 +21,5 @@
 local settingsPerm = false
 local refreshRate = 0
+local netDevices = nil
 
 local PLUGIN_SID = "urn:freebox-fr:serviceId:Freebox1"
@@ -67,5 +68,4 @@
 local function setVarIfChanged(serviceId, name, value, deviceId, default)
 	local curValue = luup.variable_get(serviceId, name, deviceId)
-
 	if ((value ~= curValue) or (curValue == nil)) then
 		luup.variable_set(serviceId, name, value, deviceId)
@@ -74,4 +74,13 @@
 		return (default or false)
 	end
+end
+
+
+local function forceVarChange(serviceId, name, value, deviceId)
+	local curValue = luup.variable_get(serviceId, name, deviceId)
+	if (value == curValue) then
+		luup.variable_set(serviceId, name, "", deviceId)
+	end
+	luup.variable_set(serviceId, name, value, deviceId)
 end
 
@@ -363,6 +372,4 @@
 
 local function checkNetworkDevices(checkSession)
-	local devices = {}
-
 	if (checkSession) then
 		openSession()
@@ -398,26 +405,34 @@
 			data = getData(request, code, table.concat(result))
 			if (data.success) then
+				local devices = {}
 				for i=1,count do
 					local id = data.result[i].id
 					devices[id] = {}
 					devices[id].name = data.result[i].primary_name
-					local ip = nil
+					devices[id].mac = ""
+					if (data.result[i].l2ident ~= nil
+							and data.result[i].l2ident.type == "mac_address") then
+						devices[id].mac = data.result[i].l2ident.id
+					end
+					devices[id].ip = ""
 					if (data.result[i].l3connectivities ~= nil) then
 						for j=1,5 do
-							if (data.result[i].l3connectivities[j] == nil) then
+							if (data.result[i].l3connectivities[j] ~= nil
+									and data.result[i].l3connectivities[j].af == "ipv4") then
+								devices[id].ip = data.result[i].l3connectivities[j].addr
 								break
-							end
-							if (data.result[i].l3connectivities[j].af == "ipv4" or ip == nil) then
-								ip = data.result[i].l3connectivities[j].addr
 							end
 						end
 					end
-					if (ip ~= nil) then
-						devices[id].ip = ip
-					else
-						devices[id].ip = "undefined ip"
+					devices[id].reachable = data.result[i].reachable
+					if (netDevices ~= nil and netDevices[id] ~= nil and devices[id].mac ~= "") then
+						if (netDevices[id].reachable and not devices[id].reachable) then
+							forceVarChange(PLUGIN_SID, "PeripheriqueInjoignable", devices[id].mac, THIS_DEVICE)
+						elseif (not netDevices[id].reachable and devices[id].reachable) then
+							forceVarChange(PLUGIN_SID, "PeripheriqueJoignable", devices[id].mac, THIS_DEVICE)
+						end
 					end
-					devices[id].reachable = data.result[i].reachable
 				end
+				netDevices = devices
 			else
 				log("Echec requête: " .. data.msg .. " ( " .. data.error_code .. " )", 2)
@@ -428,11 +443,29 @@
 	end
 
-	-- for k, v in pairs(devices) do
-		-- if (v.reachable) then
-			-- log(v.name .. " " .. v.ip .. " joignable")
-		-- else
-			-- log(v.name .. " " .. v.ip .. " injoignable")
-		-- end
-	-- end
+	local xml = "<net_devices>"
+	if (netDevices ~= nil) then
+		for k, v in pairs(netDevices) do
+			if (v.reachable) then
+				xml = xml .. "<device>"
+				xml = xml .. "<name>" .. v.name .. "</name>"
+				xml = xml .. "<ip>" .. v.ip .. "</ip>"
+				xml = xml .. "<mac>" .. v.mac .. "</mac>"
+				xml = xml .. "<reachable>true</reachable>"
+				xml = xml .. "</device>"
+			end
+		end
+		for k, v in pairs(netDevices) do
+			if (not v.reachable) then
+				xml = xml .. "<device>"
+				xml = xml .. "<name>" .. v.name .. "</name>"
+				xml = xml .. "<ip>" .. v.ip .. "</ip>"
+				xml = xml .. "<mac>" .. v.mac .. "</mac>"
+				xml = xml .. "<reachable>false</reachable>"
+				xml = xml .. "</device>"
+			end
+		end
+	end
+	xml = xml .. "</net_devices>"
+	setVarIfChanged(PLUGIN_SID, "PeripheriquesReseau", xml, THIS_DEVICE, false)
 end
 
@@ -643,7 +676,7 @@
 local function getRefreshRate()
 	local rate = tonumber(luup.variable_get(PLUGIN_SID, "FrequenceMiseAJour", THIS_DEVICE) or "")
-	if (rate == nil or rate < 5) then
+	if (rate == nil or (rate > 0 and rate < 5)) then
 		rate = 0
-		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", rate, THIS_DEVICE, false)
+		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", string.format("%d", rate), THIS_DEVICE, false)
 	end
 	refreshRate = rate
@@ -657,5 +690,5 @@
 			rate = 0
 		end
-		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", rate, THIS_DEVICE, false)
+		setVarIfChanged(PLUGIN_SID, "FrequenceMiseAJour", string.format("%d", rate), THIS_DEVICE, false)
 		local oldRate = refreshRate
 		refreshRate = rate
@@ -713,5 +746,25 @@
 	checkLanServer(false)
 
-	--checkNetworkDevices(false)
+	checkNetworkDevices(false)
+
+	-- local devices = luup.variable_get("urn:freebox-fr:serviceId:Freebox1", "PeripheriquesReseau", THIS_DEVICE) or ""
+	-- local mac = ""
+	-- local reachable = devices:match("<mac>"..mac.."</mac><reachable>(.-)</reachable>")
+	-- if (reachable == "true") then
+		-- log(mac .. " joignable")
+	-- elseif (reachable == "false") then
+		-- log(mac .. " injoignable")
+	-- else
+		-- log(mac .. " inconnu")
+	-- end
+	-- local ip = ""
+	-- reachable = devices:match("<ip>"..ip.."</ip>.-<reachable>(.-)</reachable>")
+	-- if (reachable == "true") then
+		-- log(ip .. " joignable")
+	-- elseif (reachable == "false") then
+		-- log(ip .. " injoignable")
+	-- else
+		-- log(ip .. " inconnu")
+	-- end
 
 	checkWifi(false)
Index: /trunk/D_Freebox.json
===================================================================
--- /trunk/D_Freebox.json	(revision 1)
+++ /trunk/D_Freebox.json	(revision 3)
@@ -432,4 +432,14 @@
             ]
         },
+		{
+			"Label": {
+				"lang_tag": "tabname_net_devices",
+				"text": "Peripheriques reseau"
+			},
+			"Position": "1",
+			"TabType": "javascript",
+			"ScriptName": "J_Freebox.js",
+			"Function": "showNetDevices"
+		},
         {
             "Label": {
@@ -437,10 +447,106 @@
                 "text": "Advanced"
             },
-            "Position": "1",
+            "Position": "2",
             "TabType": "javascript",
             "ScriptName": "shared.js",
             "Function": "advanced_device"
+        },
+        {
+            "Label": {
+                "lang_tag": "logs",
+                "text": "Logs"
+            },
+            "Position": "3",
+            "TabType": "javascript",
+            "ScriptName": "shared.js",
+            "Function": "device_logs"
+        },
+        {
+            "Label": {
+                "lang_tag": "notifications",
+                "text": "Notifications"
+            },
+            "Position": "4",
+            "TabType": "javascript",
+            "ScriptName": "shared.js",
+            "Function": "device_notifications"
         }
     ],
-    "DeviceType": "urn:freebox-fr:device:Freebox:1"
+    "DeviceType": "urn:freebox-fr:device:Freebox:1",
+	"eventList": {
+		"event_1": {
+			"label": "Peripherique devient joignable",
+			"serviceId": "urn:freebox-fr:serviceId:Freebox1",
+			"argumentList" : {
+				"argument_1" : {
+					"dataType": "string",
+					"defaultValue": "",
+					"name": "PeripheriqueJoignable",
+					"comparisson": "=",
+					"prefix": "Adresse MAC",
+					"suffix": ""
+				}
+			}
+		},
+		"event_2": {
+			"label": "Peripherique devient injoignable",
+			"serviceId": "urn:freebox-fr:serviceId:Freebox1",
+			"argumentList" : {
+				"argument_1" : {
+					"dataType": "string",
+					"defaultValue": "",
+					"name": "PeripheriqueInjoignable",
+					"comparisson": "=",
+					"prefix": "Adresse MAC",
+					"suffix": ""
+				}
+			}
+		}
+	},
+	"eventList2": [
+		{
+			"id": 1,
+			"label": {
+				"lang_tag": "device_reachable",
+				"text": "Peripherique devient joignable"
+			},
+			"serviceId": "urn:freebox-fr:serviceId:Freebox1",
+			"argumentList": [
+				{
+					"id": 1,
+					"dataType": "string",
+					"defaultValue": "",
+					"name": "PeripheriqueJoignable",
+					"comparisson": "=",
+					"prefix": {
+						"lang_tag": "mac_address",
+						"text": "Adresse MAC"
+					},
+					"suffix": {}
+				}
+			]
+		},
+		{
+			"id": 2,
+			"label": {
+				"lang_tag": "device_unreachable",
+				"text": "Peripherique devient injoignable"
+			},
+			"serviceId": "urn:freebox-fr:serviceId:Freebox1",
+			"argumentList": [
+				{
+					"id": 1,
+					"dataType": "string",
+					"defaultValue": "",
+					"name": "PeripheriqueInjoignable",
+					"comparisson": "=",
+					"prefix": {
+						"lang_tag": "mac_address",
+						"text": "Adresse MAC"
+					},
+					"suffix": {}
+				}
+			]
+		}
+	]
 }
Index: /trunk/S_Freebox.xml
===================================================================
--- /trunk/S_Freebox.xml	(revision 1)
+++ /trunk/S_Freebox.xml	(revision 3)
@@ -41,7 +41,31 @@
     	<dataType>boolean</dataType>
     </stateVariable>
+    <stateVariable>
+    	<name>PeripheriquesReseau</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>PeripheriqueJoignable</name>
+    	<sendEventsAttribute>yes</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>PeripheriqueInjoignable</name>
+    	<sendEventsAttribute>yes</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
+    <stateVariable>
+    	<name>PeripheriqueJoignable</name>
+    	<sendEventsAttribute>no</sendEventsAttribute>
+    	<dataType>string</dataType>
+    </stateVariable>
     <stateVariable sendEvents="no">
       <name>A_ARG_TYPE_frequence</name>
       <dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_mac</name>
+      <dataType>string</dataType>
     </stateVariable>
   </serviceStateTable>
