Serial Communication

lua-users home
wiki

Here's various code and discussions relating to controlling the RS-232 serial communication port from Lua.

Mail List Posts

Using Alien

-- Lua serial communications library.

--

-- Note: currently only works on Win32.

--

-- The implementation uses alien ( http://alien.luaforge.net/ ).

--

-- Warning: not extensively tested.

--

-- Possible improvements:

--   - decide more consistently when to raise or return on error

--     (io.open/fh:read/fh:write return, but Lua

--      Programming Gems, p.137 suggests we perhaps

--      should raise)

--   - add close() method on file handle, possibly

--     via __gc on newproxy() so that it closes upon

--     garbage collection

--

-- D.Manura. 2008-07

-- S.Slobodov, 2009-01: fixed alien interface, fixed a typo,

--    added COM10 and up, made non-blocking

-- D.Manura, 2009-01: add close(); improve exception safety some

-- S.Slobodov, 2009-01: added routines to set DTR and RTS and to query CTS, plus unbuffered transmission

-- Licensed under the same terms as Lua itself (MIT license).

-- Please post patches and improvements.





local M = {}



local alien = require "alien"



-- win32 values for CreateFile

local GENERIC_READ = 0x80000000

local GENERIC_WRITE = 0x40000000

local OPEN_EXISTING = 3



-- win32 parity values

local EVENPARITY = 2

local MARKPARITY = 3

local NOPARITY = 0

local ODDPARITY = 1

local SPACEPARITY = 4



-- win32 stop bit values

local ONESTOPBIT = 0

local ONE5STOPBITS = 1

local TWOSTOPBITS = 2



-- maps parity name to win32 parity value

local parity_to_win32 = {

  even = EVENPARITY,

  mark = MARKPARITY,

  none = NOPARITY,

  odd = ODDPARITY,

  space = SPACEPARITY

}



-- maps stop bits to win32 stop bits value

local stopbits_to_win32 = {

  [1]   = ONESTOPBIT,

  [1.5] = ONE5STOPBITS,

  [2]   = TWOSTOPBITS

}



local kernel32 = alien.load"kernel32.dll"



-- bitwise operators

-- based on http://ricilake.blogspot.com/2007/10/iterating-bits-in-lua.html

-- 1-based indexing

local function bit(p) return 2 ^ (p - 1) end

local function hasbit(x, p) return x % (p + p) >= p end

local function setbit(x, p) return hasbit(x, p) and x or x + p end

local function clearbit(x, p) return hasbit(x, p) and x - p or x end

local function changebit(x, p, b) return b and setbit(x,p) or clearbit(x,p) end





local function get_last_error()

  local FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100

  local FORMAT_MESSAGE_FROM_SYSTEM     = 0x00001000

  local FORMAT_MESSAGE_IGNORE_INSERTS  = 0x00000200



  local LANG_NEUTRAL    = 0x00

  local SUBLANG_DEFAULT = 0x01



  local function MAKELANGID(p, s) return s * 2^10 + p end



  -- C function declarations

  local FormatMessage = assert(kernel32.FormatMessageA)

  FormatMessage:types{ret ='int', abi = 'stdcall', 'int',

      'pointer', 'int', 'int', 'pointer', 'int', 'pointer'}

  local GetLastError = assert(kernel32.GetLastError)

  GetLastError:types{ret = 'int', abi = 'stdcall'}

  local LocalFree = assert(kernel32.LocalFree)

  LocalFree:types{ret = 'pointer', abi = 'stdcall', 'pointer'}



  local buf = alien.buffer(4)

  buf:set(0, buf2, 'pointer')



  local ret = FormatMessage(

    FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM

      + FORMAT_MESSAGE_IGNORE_INSERTS,

    nil,

    GetLastError(),

    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),

    buf, -- ref: msg_buf,

    0,

    nil

  )

  if ret == 0 then return "Unknown error." end

  local msg = alien.tostring(buf:get(1,'pointer'))

  LocalFree(buf:get(1, 'pointer'))



  return msg

end





local function config(h, t)

  local speed = t.speed

  local databits = t.databits

  local stopbits = t.stopbits

  local parity = t.parity

  local handshake = t.handshake



  assert(stopbits_to_win32[stopbits])

  assert(parity_to_win32[parity])

  assert(databits == 5 or databits == 6 or databits == 7 or databits == 8)

  assert(type(speed) == 'number' and speed > 0)



  -- C function declarations

  local GetCommState = kernel32.GetCommState

  GetCommState:types{ret = "int", abi = 'stdcall', "pointer", "pointer"}

  local SetCommState = assert(kernel32.SetCommState)

  SetCommState:types{ret = "int", abi = 'stdcall', "pointer", "pointer"}

  local SetCommTimeouts = assert(kernel32.SetCommTimeouts)

  SetCommTimeouts:types{ret = "int", abi = 'stdcall', "pointer", "pointer"}



  local buf = alien.buffer(4*3 + 3*2 + 3*1 + 5*3 + 2*1)

  GetCommState(h, buf)



  local offset_DCBlength = 1

  local offset_BaudRate  = 1 + 1*4

  local offset_flags     = 1 + 2*4

  local offset_XonLim    = 1 + 3*4 + 1*2

  local offset_XoffLim   = 1 + 3*4 + 2*2

  local offset_ByteSize  = 1 + 3*4 + 3*2

  local offset_Parity    = 1 + 3*4 + 3*2 + 1

  local offset_StopBits  = 1 + 3*4 + 3*2 + 2

  local offset_XonChar   = 1 + 3*4 + 3*2 + 3

  local offset_XoffChar  = 1 + 3*4 + 3*2 + 4

  local offset_ErrorChar = 1 + 3*4 + 3*2 + 5

  local offset_EofChar   = 1 + 3*4 + 3*2 + 6

  local offset_EvtChar   = 1 + 3*4 + 3*2 + 7



  buf:set(offset_BaudRate, speed, 'int')



  local bits = buf:get(offset_flags, 'int')

  bits = changebit(bits, bit(1),  true)  -- fBinary

  bits = changebit(bits, bit(2),  parity ~= 'none')  -- fParity

  bits = changebit(bits, bit(3),  handshake == 'hardware') -- fOutxCtsFlow

  bits = changebit(bits, bit(4),  false) -- fOutxDsrFlow (ok?)

  bits = changebit(bits, bit(5),  false) -- fDtrControl[1] (ok?)

  bits = changebit(bits, bit(6),  false) -- fDtrControl[2] (ok?)

  bits = changebit(bits, bit(7),  false) -- fDsrSensitivity (ok?)

  bits = changebit(bits, bit(8),  false) -- fTXContinueOnXoff  (ok?)

  bits = changebit(bits, bit(9),  handshake == 'xon/xoff') -- fOutX

  bits = changebit(bits, bit(10), handshake == 'xon/xoff') -- fInX

  bits = changebit(bits, bit(11), false) -- fErrorChar (ok?)

  bits = changebit(bits, bit(12), false) -- fNull (ok?)

  bits = changebit(bits, bit(13), false) -- fRtsControl [1]

  bits = changebit(bits, bit(14), handshake == 'xon/xoff') -- fRtsControl [2]

  bits = changebit(bits, bit(15), false) -- fAbortOnError (ok?)

  buf:set(offset_flags, bits, 'int')

  buf:set(offset_ByteSize, databits, 'byte')

  buf:set(offset_Parity, parity_to_win32[parity], 'byte')

  buf:set(offset_StopBits, stopbits_to_win32[stopbits], 'byte')



  SetCommState(h, buf)



  -- timeout on receive immediately if no data pending

  --	(so that you have a chance to do bigger an better things)

  -- http://msdn.microsoft.com/en-us/library/aa363437(VS.85).aspx

  local buf = alien.buffer(4*5)      -- _COMMTIMEOUTS:

  buf:set(1, -1, "int")              -- ReadIntervalTimeout

  buf:set(1+4*1, 0, "int")           -- ReadTotalTimeoutMultiplier

  buf:set(1+4*2, 0, "int")           -- ReadTotalTimeoutConstant

  buf:set(1+4*3, 0, "int")           -- WriteTotalTimeoutMultiplier

  buf:set(1+4*4, 0, "int")           -- WriteTotalTimeoutConstant

  SetCommTimeouts(h, buf)

end



local CloseHandle

local function open(t)

  local port = t.port

  assert(type(port) == 'string' and port:match('^COM[0-9]+$'),

    'invalid port name '..port..'; expecting e.g. "COM1"')



  local portNum = port:match("^COM([0-9]+)")

  if portNum+0 >= 10 then port = "\\\\.\\"..port end



  -- C function declarations

  local CreateFile = assert(kernel32.CreateFileA)

  CreateFile:types{ret = "pointer", abi = 'stdcall', "string",

      "int", "int", "pointer", "int", "int", "pointer"}

  CloseHandle = assert(kernel32.CloseHandle)

  CloseHandle:types{ret = 'int', abi = 'stdcall', "pointer"}



  local n = (GENERIC_READ + GENERIC_WRITE) - 2^32 + 1

  local h = CreateFile(port, n, 0, nil, OPEN_EXISTING, 0, nil)



  -- convert handle to int

  local tmp = alien.buffer(4)

  tmp:set(1, h, 'pointer')

  local hnum = tmp:get(1, 'int')

  if hnum == -1 then return nil, get_last_error() end



  local ok, msg = pcall(config, h, t)

  if not ok then

    CloseHandle(h)

    return nil, msg

  end



  return h

end

M.open = open



local function close(h)

  CloseHandle(h)

end

M.close = close



local function send(h, s)

  local WriteFile = assert(kernel32.WriteFile)

  WriteFile:types{ret="int", abi = 'stdcall', "pointer", "string",

      "int", "ref int", "pointer"}



  local bytes_written_buf = alien.buffer(4)



  local ret, nwritten = WriteFile(h, s, #s, 0, nil )

  if ret == 0 or nwritten ~= #s then

    error("failed write: " .. get_last_error())

  end

end

M.send = send



local function receive(h)

  -- C function declarations

  local ReadFile = assert(kernel32.ReadFile)

  ReadFile:types{ ret = 'int', abi = 'stdcall', "pointer",

      "ref char", "int", "ref int", "pointer"}



  local ret, char, nread = ReadFile(h, buffer_buf, 1, 0, nil )

  if ret == 0 then

    error("failed read: " .. get_last_error())

  end

  if char < 0 then char = char+256 end	-- avoid negatives

  return nread == 1 and string.char(char) or nil

end

M.receive = receive



local function receive_all(h)

  local s = ""

  while 1 do

    local c = receive(h)

    if c then

      s = s .. c

    else

      return s

    end

  end

end

M.receive_all = receive_all



local buf = ""

local function async_read_until(h, char)

  while 1 do

    local c = receive(h)

    if c then

      buf = buf .. c

      if c == char then

        local s = buf

        buf = ""

        return s

      end

    else

      return

    end

  end

end



local function SetDTR(h, set)

	local EscapeCommFunction = assert(kernel32.EscapeCommFunction)

	EscapeCommFunction:types{ret="int", abi = 'stdcall', "pointer", "int"}

	if set then

		EscapeCommFunction(h, SETDTR)

	else

		EscapeCommFunction(h, CLRDTR)

	end

end

M.SetDTR = SetDTR



local function SetRTS(h, set)

	local EscapeCommFunction = assert(kernel32.EscapeCommFunction)

	EscapeCommFunction:types{ret="int", abi = 'stdcall', "pointer", "int"}

	if set then

		EscapeCommFunction(h, SETRTS)

	else

		EscapeCommFunction(h, CLRRTS)

	end

end

M.SetRTS = SetRTS



local function GetCTS(h)

	local GetCommModemStatus = assert(kernel32.GetCommModemStatus)

	GetCommModemStatus:types{ret = "int", abi = "stdcall", "pointer", "ref int"}

	local _, CTS = GetCommModemStatus(h, 0)

	return CTS == CTS_ON

end

M.GetCTS = GetCTS



local function TransmitChar(h, c)

	local TransmitCommChar = assert(kernel32.TransmitCommChar)

	TransmitCommChar:types{ret="int", abi = 'stdcall', "pointer", "int"}

	if not TransmitCommChar(h, c) then return nil, get_last_error()

	else return true end

end

M.TransmitChar = TransmitChar



return M

--DavidManura

Using serial

The following is a sample code that redirects all traffic between a serial port and a local socket port. It works for me, but YMMV. Run from Lua command prompt. The outer while loop is to allow repeated reconnects by an ip client without having to restart the script.

local s=require "socket"

local com=require "serial"



if arg[1] == nil then

	print("Usage: "..arg[0].." <socket port> "..

	      "[<com port> [<speed> [<databits> [<parity> [<stopbits>]]]]]")

	print("Example: "..arg[0].." 10000 COM1 38400 8 none 1")

	return

end



local port = tonumber(arg[1]) or 10000

local comport = arg[2] or "COM1"

local speed = tonumber(arg[3]) or 38400

local databits = tonumber(arg[4]) or 8

local parity = arg[5] or "none"

local stopbits = tonumber(arg[6]) or 1



local sp, errmsg = assert(com.open{port=comport, speed=speed,

    databits=databits, stopbits=stopbits, parity=parity})



while true do

	local server = assert(s.bind("127.0.0.1", port ))

	print("waiting for connection on port", port)

	local client = assert(server:accept())

	print("connected")

	client:settimeout(0.1)

	while true do

		local data, errmsg, partial = client:receive()

		if errmsg == "closed" then

			print("connection closed")

			client:close()

			server:close()

			break

		elseif errmsg == "timeout" then data = partial

		elseif errmsg then error(errmsg) end

		if data and data ~= "" then

			for i = 1, #data do

				local c = data:sub(i,i)

				com.send(sp, c)

			end

		end

		data = com.receive_all(sp)

		if data and data ~= "" then

			client:send(data)

		end

	end

end

-- Sergei Slobodov

Using librs232lua

git clone git://github.com/ynezz/librs232.git
http://github.com/ynezz/librs232/downloads

-- Petr Štetiar <ynezz@true.cz>

LuaSys

Others


RecentChanges · preferences
edit · history
Last edited June 21, 2012 7:41 am GMT (diff)