Documentation
-- Module to make GPIO via the sysfs gpio class interface easy
-- for simple Lua applications.

-- simplest minimal module framework, just return M
local M = {
  _NAME=(...),
  _VERSION="v0.001",
}


-- Utility functions private to the module
local P = require "posix"

-- write str to the named file
function putfile(name,str)
  local f = assert(io.open(name,"w"))
  f:write(str)
  f:close()
end

-- read the named file and return its content
function getfile(name)
  local f = assert(io.open(name,"r"))
  local str f:read"*a"
  f:close()
  return str
end

-- transform a list into a set for quick lookup
function SET(t)
  local s = {}
  for _,v in ipairs(t) do
    s[v] = true
  end
  return s
end


-- container for GPIO functions, alias to M
local gpio=M
gpio.pins1_1 = SET{ -- Original RPi A, B
     0,  1,          4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,         21, 22, 23,
    24, 25,       }

gpio.pins1_2 = SET{ -- Rev 2 A, B
             2,  3,  4,          7,
     8,  9, 10, 11,         14, 15,
        17, 18,             22, 23,
    24, 25,     27}

gpio.pins2_0 = SET{ -- A+, B+, 2A, 2B
             2,  3,  4,  5,  6,  7,
     8,  9, 10, 11, 12, 13, 14, 15,
    16, 17, 18, 19, 20, 21, 22, 23,
    24, 25, 26, 27}

-- default to RPi B+ board
-- TODO: Read /proc/cpuinfo and get this right based on the
--       Hardware and Revision fields. For now, just assume
--       what I have will work.
gpio.pins = gpio.pins2_0

-- export a sysfs pin to userland
function gpio.export(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/export",tostring(pin))
end

-- return a sysfs pin to the kernel
function gpio.unexport(pin)
  assert(gpio.pins[pin], "Can't use pin")
  putfile("/sys/class/gpio/unexport",tostring(pin))
end

-- userland path to a specific pin's atribute file
function gpio.pinpath(pin,attr)
  return ("/sys/class/gpio/gpio%d/%s"):format(pin,attr)
end

-- set pin direction
function gpio.direction(pin,dir)
  local path = gpio.pinpath(pin, "direction")
  --print("Setting "..path.." to "..dir)
  putfile(path, dir)
end

-- set pin edge detector
function gpio.edge(pin,edge)
  local path = gpio.pinpath(pin, "edge")
  --print("Setting "..path.." to "..edge)
  putfile(path, edge)
end

-- blocking wait with timeout for an edge on a pin
function gpio.wait(pin)
  assert(gpio.pins[pin], "Can't use pin")
  local path = gpio.pinpath(pin, "value")
  if not gpio.pinfd then 
    gpio.pinfd = P.open(path, P.O_RDONLY)
  end
  local p = gpio.pinfd
  if not gpio.fds then gpio.fds = {} end
  if not gpio.fds[p] then gpio.fds[p] = {events={}, revents={}} end
  gpio.fds[p].events.PRI=true
  local ret = P.poll(gpio.fds, 10000)
  if ret == 0 then return 0 end
  P.lseek(p,0,P.SEEK_SET)
  ret = tonumber(P.read(p,8))
  --P.close(p)
  --gpio.pinfd = nil
  return ret
end 

--Example
--[===[

-- Use pin P1-28 aka GPIO7 for SPLear alerts
-- local gpio = require "sysfsgpio"
gpio.export(7)
gpio.direction(7,"in")
gpio.edge(7,"rising")

while(true) do
  local r = gpio.wait(7)
  if r == 0 then
    --print"timeout"
  elseif r==1 then
    local tim = P.gettimeofday()
    print("eek "..tim.sec..'.'..tim.usec)
  else
    print("???", r)
  end
end

if gpio.pinfd then P.close(gpio.pinfd) end

--]===]


-- TODO: Do some minimal validation that there actually is
--       a sysfs and error out if not.

return M