Sending ASTM messages using Translator is not much different from sending HL7 messages. For this purpose we will use well known ‘llp’ module, rename it and modify to match ASTM protocol particulars and exclude LLP specific elements.
This Example Dependencies
In order to test this project, it requires to run active ASTM TCP Server (Listener) and Iguana Webserver to do HTTP POST of ASTM messages to Iguana.
Data presentation
Suggested example will read initial ASTM message data from file (… or from ‘Sample Data’ when working in Translator IDE.) Depending on type of file system, the end-of-line can be <CR> or <LF> or combination of both.
Our testdata can be seen below; and one can note that in this data the end-of-line is 0x0A character (AKA ‘\n’ or <LF>.)

Thus in module main we will examine and normalize input ASTM message frames; calculate and concatenate respective checksum digits; and pipe result to sending module.
Because data itself includes numerous control characters, it could optionally be saved as binary file instead of text.
If data had <CR> instead of <LF> this would make our example more simple, but let’s see below how to handle non-standard terminator.
Frames must be surrounded by leading STX and trailing ETX, or ETB. This sample ‘ASTM Sender’ has no way to know if frame has to be terminated with ETX or ETB unless it has been promptly specified in data itself.
Translator as ASTM TCP Client
For impatient complete ASTM_sender_To_Translator.zip project is available for download.
Modules
We call four modules.
- The astm_send module handles ASTM specific protocol to send a message
- The astn_tcp module handles TCP communications
- The astmutil module contains functions to calculate checksum digits, write debug logs, and send HTTP POST request to Iguana Webserver
- The stringutil module contains few string manipulation functions
Module Dependencies
Modules don’t ‘require’ external to Iguana Lua modules.
Configuration
Three places which will require custom configuration:
- Please remember to adjust TCP host and port values in function main() to match host and port of ASTM TCP Server that ASTM messages will be sent to.
- The webserver access credential in function astmutil.postJson () in module astmutil may have to be adjusted as well.
- The function calculating checksum digits may have to be adjusted to match specific interface design. In this example Checksum calculation is defined for ‘\r’ not ‘\n’ delimiters. Checksum is also defined to exclude <STX>, but to include either <ETX>, or <ETB>. In another environment it potentially can be different. Then algorithm in function astmutil.checksum() in module astmutil has to be adjusted.
Main module

require('astm_send')
require('stringutil')
require('astmutil')
-- Data (frames) must be enclosed in leading STX and trailing ETX, or ETB.
-- 'ASTM Sender' has no way to know if frame has to be terminated with ETX
-- or ETB unless it has been promptly specified in data itself.
function main(Data)
local data = Data:split('\002')
table.remove(data,1)
for i=1,#data do
data[i]:trimRNL()
data[i]='\002'..data[i]
end
data = addchecksum(data)
local Success, err = astm_send.send(
data,
'192.168.5.4', -- host
6559, -- port
10, -- timeout
true) -- live
if not Success then
iguana.logError(err)
end
end
function addchecksum(data)
for i=1,#data do
local d = data[i]:trimRNL()
local ck=astmutil.checksum(d)
data[i]=d..ck..'\r'
end
return data
end
Modules code
The astm_send module.
require('astm_tcp')
local function astm_initiator(param)
local dctrl={5,4} -- flow control ENQ and EOT values, respectively
local gs
local inconversation=false
local tcp = astm_tcp.connect{host=param[2],port=param[3],timeout=param[4],live=param[5]}
local function sendit(d)
if type(d) == 'string' then
iguana.logDebug ('sending '..d)
tcp:send(d)
else
tcp:send(string.char(d)..'\n')
end
if d == dctrl[2] then
return
end
while true do
local s = tcp:recv()
gs= s
if gs then
return gs
end
end
end
gs = sendit(dctrl[1])
iguana.logDebug ('sent ENQ signal')
if not inconversation and string.byte(gs) ==6 then
iguana.logDebug ('received ASTM ACK')
gs=nil
inconversation=true
end
if not inconversation and string.byte(gs) ==21 then
iguana.logDebug ('received ASTM NACK')
gs=nil
end
local repeats
local function repeatsends(d)
repeats=repeats-1
gs=sendit(d)
if string.byte(gs) == 6 then
iguana.logDebug ('received ASTM ACK')
return
elseif repeats > 0 then
iguana.logDebug ('received ASTM NACK')
repeatsends(d)
else
if repeats == 0 then
iguana.logError('failed to transmit frame "'..d..'"')
end
return
end
end
local function sendpayload()
for k,v in ipairs(param[1]) do
repeats = 5
repeatsends(param[1][k]..'\n')
end
end
if inconversation then
sendpayload()
gs = sendit(dctrl[2])
iguana.logDebug ('sent EOT signal')
elseif not inconversation then
iguana.logError('remote not accepting') -- some repeat logic could be nice to have.
return
end
tcp:close()
end
-- public
astm_send = {}
function astm_send.send(data,Host,Port,Timeout,Live)
Success, err = pcall(astm_initiator,{data,Host,Port,Timeout,Live})
return Success, err
end
The astm_tcp module.
--
-- Here we expose a single function, connect(), that opens
-- an astm_tcp connection to a remote host, and returns a table
-- to interact with.
--
-- When debugging, it actually returns a fake connection,
-- which can be sent ASTM messages, and
-- replies with ACKs (one ACK per message sent).
--
-- If you want to test a real astm_tcp connection in the editor,
-- pass live=true along with your connection settings.
--
-- require('astm_tcp')
--
-- local s = astm_tcp.connect{host='frink',port=8086}
-- s:send(Data)
-- local Ack = s:recv()
-- s:close()
--
__astm_tcp = {
send_astm_tcp = function(s, msg)
local sent, text = 0, msg
repeat
sent = sent + s:send(text, sent+1)
until sent >= text:len()
return sent
end;
recv_astm_tcp = function(s, buf)
buf=s:recv()
return buf
end;
real_meta = {
__index = {
send = function(self, msg)
return __astm_tcp.send_astm_tcp(self.s, msg)
end;
recv = function(self)
local msg, skipped
msg = __astm_tcp.recv_astm_tcp(self.s, self.buf)
return msg
end;
close = function(self)
self.s:close()
end
}
};
--
-- Metatable for Simulation
--
simulation_meta = {
__index = {
send = function(self, msg)
if not self.connected then
error('not connected', 2)
end
self.sent = msg
return msg:len()
end;
recv = function(self)
if not self.connected then
error('not connected', 2)
elseif not self.sent then
error('timeout', 2)
else
local got = '\006' -- ASTM flow control ACK
self.sent = nil
return got
end
end;
close = function(self)
self.connected = false
end
}
};
--
-- Error Checking
--
check_arg = function(args, k, t, optional)
local help = [[Connect to a remote astm_tcp host.
Takes a table with the following required entries:
'host' - the hostname of the remote site
'port' - the port on the remote site
and optionally these entries:
'timeout' - maximum wait time, in seconds (default 5s)
'live' - create live astm_tcp connections in the editor
e.g. local s = astm_tcp.connect{host='hostname',port=8086}
s:send(Data)
local Ack = s:recv()
s:close()
]]
if not args then
error(help, 3)
elseif type(args) ~= 'table' then
error('Parameter 1 is not a table.\n'..help, 3)
elseif not optional and not args[k] then
error("Parameter '"..k.."' is required.\n"..help, 3)
elseif args[k] and type(args[k]) ~= t then
error("Parameter '"..k.."' should be a "..t..'.\n'..help, 3)
end
end;
}
--
-- Public Interface
--
astm_tcp = {}
function astm_tcp.connect(args)
local required, optional = false, true
__astm_tcp.check_arg(args, 'host', 'string', required)
__astm_tcp.check_arg(args, 'port', 'number', required)
__astm_tcp.check_arg(args, 'timeout', 'number', optional)
__astm_tcp.check_arg(args, 'live', 'boolean', optional)
if args.live or not iguana.isTest() then
-- Normal behaviour (in running channel).
args.live = nil
local Success, Socket = pcall(net.tcp.connect, args)
if not Success then
-- raise error to caller level
error(Socket, 2)
end
return setmetatable({
s = Socket,
buf = '', -- input buffer.
}, __astm_tcp.real_meta)
else
-- Simulate behaviour while editing.
return setmetatable({
connected = true,
}, __astm_tcp.simulation_meta)
end
end
The astmutil module.
astmutil={}
function astmutil.checksum(s)
-- Some test data may have wrong delimiters, e.g. rn or single n instead of r.
-- Checksum calculation is defined for r not n delimiters.
-- Checksum below is defined to strip <STX>, but not <ETX>, nor <ETB>.
s=s:gsub('n','r')
local function zfill(s,N)
if s:len() < N then
repeat
s = '0' .. s
until s:len() == N
end
return s
end
local r=0
for b in string.gfind(s, ".") do
if string.byte(b) == 2 then r=-2 end -- equivalent to strip <STX> which is x02
r=string.byte(b)+r
end
r=string.format("%x", tostring(((r)%256))):upper()
r=zfill(r,2)
return r
end
function astmutil.validate(s)
local a = checksum(s:sub(1,-3))
local b = s:sub(-2,-1)
if a == b then
return true
end
end
local function timestamp()
local a,b = math.modf(os.clock())
if b==0 then
b='000'
else
b=tostring(b):sub(3,5)
end
local tf=os.date('%Y-%m-%d %H:%M:%S.',os.time())
return (tf..b)
end
function astmutil.logs(s)
local function writeBinary(binData)
OUTPUT_DIR = 'C:listener2web'
FILE_PATTERN = 'astmListenerDebug.log'
fPath=OUTPUT_DIR..''
local fn=fPath..FILE_PATTERN
local f = io.open(fn, "ab")
f:write(binData)
f:close()
end
local s = timestamp()..'t'..s..'nn'
print(s) --[[ Please note that Windows cannot display
this output properly in Command Prompt window. Rather
read this output from suggested above log file or use a
debugger. Having said that, this output will show all right
in OS X terminal or in any *nix shell.]]
writeBinary(s)-- should be wrtten to file or so ..., in production version.
end
function astmutil.postJson (s)
local ltn12 = require 'ltn12'
local http = require 'socket.http'
local requestbody = s
local responsebody
b, c, h = http.request {
url = "http://'admin':'password'@192.168.5.4:6545/astm",
method = 'POST',
headers = {
["Content-Type"] = "application/json",
["Content-Length"] = tostring(string.len(requestbody))
},
source = ltn12.source.string(requestbody),
sink = ltn12.sink.table(responsebody)
}
if c == 200 then astmutil.logs('POST retuned OK') end
end
The stringutil module: download the latest version of the stringutil module from our code repository.