PageRenderTime 3696ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 1ms

/IrServer/IrServerApplet.lua

http://irserversb.googlecode.com/
Lua | 522 lines | 353 code | 81 blank | 88 comment | 55 complexity | f4ea8f4dfe82a4dcb5a8ebb0f8ca6753 MD5 | raw file
Possible License(s): GPL-3.0
  1. --[[
  2. =head1 NAME
  3. applets.IrServer.IrServerApplet - IrServer Applet for SqueezeBox Duet Controllers
  4. =head1 DESCRIPTION
  5. This applet listens on a TCP port for a simple command language
  6. that in the interprets as commands to send to its infrared transmitter
  7. using /usr/bin/testir. You can visit:
  8. http://IPADDR:8174/help
  9. for a succinct help message on the command language understood.
  10. Note that Phillips-type (RC5|RC6) commands appear not to be supported
  11. by the underlying irtx driver on the controller -- see this thread:
  12. http://forums.slimdevices.com/showthread.php?t=40367
  13. This includes some rudimentary support for making RC6 remote work,
  14. but I never got it to actually turn on my XBox360.
  15. =head1 AUTHOR
  16. Greg J. Badros - badros@cs.washington.edu
  17. Copyright (C) 2009 Greg J. Badros
  18. =head1 LICENSE
  19. This file is part of IrServer
  20. IrServer is free software: you can redistribute it and/or modify
  21. it under the terms of the GNU General Public License as published by
  22. the Free Software Foundation, either version 3 of the License, or
  23. (at your option) any later version.
  24. IrServer is distributed in the hope that it will be useful,
  25. but WITHOUT ANY WARRANTY; without even the implied warranty of
  26. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  27. GNU General Public License for more details.
  28. You should have received a copy of the GNU General Public License
  29. along with IrServer. If not, see <http://www.gnu.org/licenses/>.
  30. =cut
  31. --]]
  32. -- stuff we use
  33. local tostring, ipairs, tonumber = tostring, ipairs, tonumber
  34. local oo = require("loop.simple")
  35. local io = require("io")
  36. local string = require("string")
  37. local math = require("math")
  38. local Applet = require("jive.Applet")
  39. local Timer = require("jive.ui.Timer")
  40. local Icon = require("jive.ui.Icon")
  41. local Label = require("jive.ui.Label")
  42. local Window = require("jive.ui.Window")
  43. local Popup = require("jive.ui.Popup")
  44. local log = require("jive.utils.log").addCategory("irserver", jive.utils.log.DEBUG)
  45. local socket = require("socket")
  46. local os = require("os")
  47. local Framework = require("jive.ui.Framework")
  48. module(..., Framework.constants)
  49. oo.class(_M, Applet)
  50. local label = nil
  51. local server = nil
  52. local ip, port
  53. local numCommands = 0
  54. local window, popup
  55. local timer
  56. -- ah = asHex, returns a "space" signal
  57. function ah(n)
  58. return n and string.format("0x%x", n) or ""
  59. end
  60. -- assumes n < 0x80000000, and returns a "mark" signal
  61. function aH(n)
  62. return n and ah(n + 0x80000000) or ""
  63. end
  64. -- see http://winlirc.sourceforge.net/technicaldetails.html
  65. function setStrings(d)
  66. d.str_carrier = ah(d.ir_carrier)
  67. d.str_gap = ah(d.ir_gap)
  68. d.str_ptrail = aH(d.ir_ptrail)
  69. if (d.mode == "rc6") then
  70. d.str_header = aH(d.ir_header_1) .. " " .. ah(d.ir_header_2) .. " " .. aH(d.ir_one_1) .. " " .. ah(d.ir_one_2)
  71. if (d.ir_ptrail == 0) then
  72. d.str_ptrail = ""
  73. end
  74. else
  75. d.str_header = aH(d.ir_header_1) .. " " .. ah(d.ir_header_2)
  76. end
  77. if (d.mode == "rc6") then
  78. -- untested
  79. -- see http://www.sbprojects.com/knowledge/ir/rc6.htm
  80. d.str_one = aH(d.ir_one_1) .. " " .. ah(d.ir_one_2)
  81. d.str_zero = ah(d.ir_zero_1) .. " " .. aH(d.ir_zero_2)
  82. elseif (d.mode == "rc5") then
  83. -- untested
  84. -- see http://www.sbprojects.com/knowledge/ir/rc5.htm
  85. d.str_one = ah(d.ir_one_1) .. " " .. aH(d.ir_one_2)
  86. d.str_zero = aH(d.ir_zero_1) .. " " .. ah(d.ir_zero_2)
  87. else
  88. -- this is tested -- for Sony IR protocol, space-encoding
  89. d.str_one = aH(d.ir_one_1) .. " " .. ah(d.ir_one_2)
  90. d.str_zero = aH(d.ir_zero_1) .. " " .. ah(d.ir_zero_2)
  91. end
  92. -- must happen after setting d.str_{one,zero}
  93. d.str_pdata = ""
  94. if (d.pdata_bits and d.pdata_bits > 0) then
  95. local pdata_binary = pdataToBinary(d.pdata, d)
  96. for i,v in ipairs(pdata_binary) do
  97. d.str_pdata = d.str_pdata .. " " .. v
  98. end
  99. end
  100. log:debug("setstrings: device ir_carrier = " .. tostring(d.ir_carrier))
  101. log:debug("setstrings: device freq = " .. d.str_carrier)
  102. log:debug("setstrings: device header = " .. d.str_header)
  103. log:debug("setstrings: device pdata = " .. d.str_pdata)
  104. end
  105. -- use pioneer amp as the defaults
  106. local device = {}
  107. device.ir_carrier = 40000
  108. device.ir_gap = 25162
  109. device.ir_ptrail = 554
  110. device.ir_header_1, device.ir_header_2 = 8500, 4200
  111. device.ir_one_1, device.ir_one_2 = 550, 1540
  112. device.ir_zero_1, device.ir_zero_2 = 550, 500
  113. device.ctrl_bits = 32
  114. device.ctrl_constlen = false
  115. device.ctrl_repeat = 1
  116. device.pdata_bits = 0
  117. setStrings(device)
  118. local devPioneerAmp = device
  119. local devSonyTV = {}
  120. devSonyTV.ir_carrier = 40000
  121. devSonyTV.ir_gap = 44881
  122. devSonyTV.ir_ptrail = nil
  123. devSonyTV.ir_header_1, devSonyTV.ir_header_2 = 2400, 588
  124. devSonyTV.ir_one_1, devSonyTV.ir_one_2 = 1200, 600
  125. devSonyTV.ir_zero_1, devSonyTV.ir_zero_2 = 600, 600
  126. devSonyTV.ctrl_bits = 12
  127. devSonyTV.ctrl_constlen = true
  128. devSonyTV.ctrl_repeat = 2
  129. devSonyTV.pdata_bits = 0
  130. setStrings(devSonyTV)
  131. -- initialize some built-in devices; these can be overridden later
  132. devices = {}
  133. devices["sonytv"] = devSonyTV
  134. devices["pioneeramp"] = devPioneerAmp
  135. -- from SetupSSHApplet.lua
  136. function _getIPAddress()
  137. local ipaddr
  138. local cmd = io.popen("/sbin/ifconfig eth0")
  139. for line in cmd:lines() do
  140. ipaddr = string.match(line, "inet addr:([%d%.]+)")
  141. if ipaddr ~= nil then break end
  142. end
  143. cmd:close()
  144. return ipaddr or "?.?.?.?"
  145. end
  146. function tobinary(n, dev)
  147. local answer = {}
  148. if (dev.lsb_first) then
  149. local i = 0
  150. while i < dev.ctrl_bits do
  151. answer[i] = n % 2 == 0 and dev.str_zero or dev.str_one
  152. n = math.floor(n/2)
  153. i = i + 1
  154. if (n == 0 and dev.ctrl_constlen == false) then
  155. break
  156. end
  157. end
  158. else
  159. local i = dev.ctrl_bits
  160. while i > 0 do
  161. answer[i] = n % 2 == 0 and dev.str_zero or dev.str_one
  162. n = math.floor(n/2)
  163. i = i - 1
  164. if (n == 0 and dev.ctrl_constlen == false) then
  165. break
  166. end
  167. end
  168. end
  169. return answer
  170. end
  171. function pdataToBinary(n, dev)
  172. local answer = {}
  173. -- this assumes const len pdata sections
  174. if (dev.lsb_first) then
  175. local i = 0
  176. while i < dev.pdata_bits do
  177. answer[i] = n % 2 == 0 and dev.str_zero or dev.str_one
  178. n = math.floor(n/2)
  179. i = i + 1
  180. end
  181. else
  182. local i = dev.pdata_bits
  183. -- n = n * 2 TODO
  184. while i > 0 do
  185. answer[i] = n % 2 == 0 and dev.str_zero or dev.str_one
  186. n = math.floor(n/2)
  187. i = i - 1
  188. end
  189. end
  190. return answer
  191. end
  192. function fullcmd(n, dev)
  193. if (n == nil) then
  194. return nil
  195. end
  196. log:debug("fullcmd: device pdata = " .. dev.str_pdata)
  197. log:debug("fullcmd: device freq = " .. dev.str_carrier)
  198. log:debug("fullcmd: device header = " .. dev.str_header)
  199. log:debug("fullcmd: n = " .. tostring(n))
  200. local answer = dev.str_carrier .. " "
  201. local codes = dev.str_header .. dev.str_pdata
  202. local binary = tobinary(n,dev)
  203. for i,v in ipairs(binary) do
  204. codes = codes .. " " .. v
  205. end
  206. codes = codes .. " " .. dev.str_ptrail .. " " .. dev.str_gap .. " "
  207. for r = 1, dev.ctrl_repeat do
  208. answer = answer .. codes
  209. end
  210. log:debug("fullcmd: answer = " .. answer)
  211. return answer
  212. end
  213. function menu(self, menuItem)
  214. log:debug("irserver started")
  215. -- log:debug("fullcmd-amp " .. fullcmd(0xA55A38C7, devPioneerAmp))
  216. -- log:debug("fullcmd-sony " .. fullcmd(0x070, devSonyTV))
  217. -- Popup a little display
  218. popup = Popup("popupIcon")
  219. -- popup:setAllowScreensaver(false)
  220. -- popup:setAlwaysOnTop(true)
  221. -- popup:setAutoHide(true)
  222. -- popup:setTransparent(false)
  223. log:info("irserver binding to 8174")
  224. -- load namespace
  225. -- create a TCP socket and bind it to the local host, at any port
  226. server = socket.bind("*", 8174)
  227. -- find out which port the OS chose for us
  228. ip, port = server:getsockname()
  229. ipaddr = _getIPAddress()
  230. -- print a message informing what's up
  231. log:info("Please telnet to "..ipaddr..":"..port)
  232. --FIXME, this window does not layout correctly (Bug 5412)
  233. local icon = Icon("iconConnecting")
  234. local text = Label("text", "Httpd IRServer - "..ipaddr..":"..tostring(port))
  235. label = Label("text", "\nNum commands: "..tostring(numCommands))
  236. popup:addWidget(icon)
  237. popup:addWidget(label)
  238. popup:addWidget(text)
  239. timer = Timer(500, function()
  240. log:debug("runserver1")
  241. self:runServer()
  242. end, true)
  243. timer:start()
  244. popup:addListener(EVENT_KEY_PRESS | EVENT_MOUSE_PRESS,
  245. function(event)
  246. server:close()
  247. log:debug("closing on key_press")
  248. popup:hide()
  249. return EVENT_UNUSED
  250. end)
  251. self:tieAndShowWindow(popup)
  252. return popup
  253. end
  254. function nonnegnum(w)
  255. if (w == nil) then
  256. return nil
  257. end
  258. -- n.b. that I need the 16 to specify hex here, else 0x80000000 and up come out negative
  259. -- (that is not the behaviour I get on my windows box lua 5.1 impl, but is what the squeezeplayer
  260. -- and the on-board jive seem to do)
  261. local n = tonumber(w, 16)
  262. if (n == nil or n < 0) then
  263. return nil
  264. end
  265. return n
  266. end
  267. function handleCommands(client, line, dev)
  268. for w in string.gmatch(line, "0x%x+") do
  269. local c = fullcmd(nonnegnum(w), dev)
  270. if (c ~= nil) then
  271. sendCommand(client, c)
  272. end
  273. end
  274. end
  275. function sendCommand(client, cmd)
  276. log:info("/usr/bin/testir "..cmd)
  277. os.execute("/usr/bin/testir "..cmd)
  278. client:send(cmd .. "\n")
  279. numCommands = numCommands + 1
  280. label:setValue("\nNum commands: "..tostring(numCommands))
  281. log:debug("set num commands ", numCommands)
  282. end
  283. function runServer(self)
  284. local full_command = nil
  285. local response = nil
  286. -- wait for a connection from any client
  287. server:settimeout(0.5)
  288. local client = server:accept()
  289. repeat
  290. if client ~= nil then
  291. -- make sure we don't block waiting for this client's line
  292. client:settimeout(4)
  293. -- receive the line
  294. local line, err = client:receive()
  295. log:debug("raw line = "..(line or "nil")..", err="..(err or "nil"))
  296. -- if there was no error, send it back to the client
  297. if not err then
  298. line, changes = string.gsub(line, "^GET /", "", 1)
  299. line, changes = string.gsub(line, " HTTP/1.[0-9]$", "", 1)
  300. line, changes = string.gsub(line, "+", " ")
  301. line, changes = string.gsub(line, "%%20", " ")
  302. line, changes = string.gsub(line, "?", " ")
  303. line = string.lower(line)
  304. if line == "quit" then
  305. client:close()
  306. server:close()
  307. popup:hide()
  308. window:hide()
  309. return nil
  310. end
  311. if line == "help" then
  312. client:send("HTTP/1.0 200 OK\r\n\r\nIrServer Help:\n"
  313. .. "testir RAW ARGUMENTS TO /usr/bin/testir\n"
  314. .. "setdev [NEWDEVNAME] [mode:rc[56]] CARRIER GAP PTRAIL HDR1 HDR2 ONE1 ONE2 ZERO1 ZERO2 NUMBITS CONSTLEN REPEAT PDATABITS PDATA...\n"
  315. .. "devcmd DEVNAME CMD1 ...\n"
  316. .. "command CMD1 ...\n"
  317. .. "quit\n"
  318. .. "\n\n All '...'s can be replaced by hex commands (e.g. 0x070)")
  319. break
  320. end
  321. line, changes = string.gsub(line, "^testir ", "", 1)
  322. if (changes > 0) then
  323. log:debug("testir ".. line)
  324. full_command = line
  325. break
  326. end
  327. -- "setdev" newdevname carrier gap ptrail hdr1 hdr2 one1 one2 zero1 zero2 numbits constlen repeat
  328. line, changes = string.gsub(line, "^setdev ", "", 1)
  329. if (changes > 0) then
  330. log:debug("setdev ".. line)
  331. local devparams = {}
  332. local i = 1
  333. local devname = nil
  334. local mode = ""
  335. line, changes = string.gsub(line, "^([_%w]+) ", function (s)
  336. devname=s
  337. return ""
  338. end)
  339. line, changes = string.gsub(line, "^mode:(rc[56]) ", function (s)
  340. mode = s
  341. return ""
  342. end)
  343. for w in string.gmatch(line, "0x%x+") do
  344. devparams[i] = nonnegnum(w)
  345. i = i + 1
  346. end
  347. device = {}
  348. device.mode = mode
  349. device.ir_carrier = devparams[1]
  350. device.ir_gap = devparams[2]
  351. device.ir_ptrail = devparams[3]
  352. device.ir_header_1, device.ir_header_2 = devparams[4], devparams[5]
  353. device.ir_one_1, device.ir_one_2 = devparams[6], devparams[7]
  354. device.ir_zero_1, device.ir_zero_2 = devparams[8], devparams[9]
  355. device.ctrl_bits = devparams[10] or 16
  356. device.ctrl_constlen = devparams[11] or true
  357. device.ctrl_repeat = devparams[12] or 1
  358. device.pdata_bits = devparams[13] or 0
  359. device.pdata = devparams[14] or 0
  360. log:debug("device mode = " .. device.mode)
  361. log:debug("devparams[1] = " .. devparams[1])
  362. log:debug("devparams[2] = " .. devparams[2])
  363. log:debug("device.ir_carrier = " .. device.ir_carrier)
  364. setStrings(device)
  365. if (devname ~= nil) then
  366. devices[devname] = device
  367. log:debug("stored new device as devname="..devname)
  368. end
  369. response = "did setdev " .. line
  370. local command = nonnegnum(devparams[15])
  371. if (command ~= nil) then
  372. full_command = fullcmd(command, device)
  373. end
  374. break
  375. end
  376. -- "devcmd" DEVICENAME command [or 0x0]
  377. line, changes = string.gsub(line, "^devcmd ", "", 1)
  378. if (changes > 0) then
  379. log:debug("devcmd ".. line)
  380. local devname = nil
  381. line, changes = string.gsub(line, "^([_%w]+) ", function(s)
  382. devname = s
  383. return ""
  384. end)
  385. if (devname and devices[devname] ~= nil) then
  386. device = devices[devname]
  387. handleCommands(client, line, device)
  388. else
  389. log:error("could not handle devname from line = "..line)
  390. end
  391. break
  392. end
  393. -- "command" 0xHEX
  394. line, changes = string.gsub(line, "^command ", "", 1)
  395. if (changes > 0) then
  396. log:debug("command ".. line)
  397. handleCommands(client, line, device)
  398. break
  399. end
  400. end
  401. log:debug("line = '" .. (line or "nil") .. "'")
  402. end
  403. until true
  404. if (full_command ~= nil and client ~= null) then
  405. sendCommand(client, full_command)
  406. else
  407. if (response ~= nil) then
  408. log:debug("no command - " .. response)
  409. end
  410. if (client ~= nil and response ~= nil) then
  411. client:send(response)
  412. end
  413. end
  414. -- done with client, close the object
  415. if (client ~= null) then
  416. client:close()
  417. end
  418. timer = Timer(500, function()
  419. -- log:debug("runserver1")
  420. self:runServer()
  421. end, true)
  422. timer:start()
  423. end