Posted 26 November 2012 - 05:00 PM
This is a post seeking serious discussion of the creation of a standard of sorts for terminal redirection over rednet (TRoR hereafter).
Terminal Redirection over Rednet is a mostly unexplored area with a huge potential set of benefits. The creation of a standard system of interaction between sender and receiver will greatly simplify any efforts to create compatible programs using TRoR. To that end, I propose the following standard packet structure:
Please note that any non-semicolon character can be placed between the colon and the semicolon by either end, which MAY be used for any purpose, but standard-compliant TRoR programs SHALL NOT malfunction if any characters are present there. This basically means that to get the message contents, a shell.match(message, ";(.*)") is suggested.
Here is a list of the two-character packet codes and their uses:
So, an example packet exchange might look like:
Here is a packet type/name translation table, for more human-readable handling of these packets, which we will be using in our example implementation:
So, that's basically the end of the proposed standard, on to the example implementation:
And here are a couple of utility functions that we'll use for easier handling of the packet format:
Now, to redirect terminal output to the client, we will need to construct a terminal redirect table with all of the normal term functions, except that it sends them to the client rather than outputting anything on the screen. Here is a function that constructs a new redirect table. The function is passed a computer ID and constructs a redirect target that sends all of the term commands to that computer ID. Note that it references the two functions above, so if using this table, be sure to include those functions above this one.
So, to handle these packets on the client end, we have a simple function that takes slightly-processed information about these packets and responds appropriately. It takes the server computer ID, the human-readable packet type, and the value sent with it as parameters. Note that this also uses the send() helper function above.
Here's a stub of a processing loop for receiving those packets. Note that this references the packetConversion table above.
So, that's the proposed TRoR standard and the example implementation, which are used in my net shell program (nsh), to great effect. I propose that this TRoR specification be adopted as a semi-formal standard, and would like to request serious commentary on the standard, but not so much the example implementation (as any standard-compliant program would be free to implement it in its own way).
Terminal Redirection over Rednet is a mostly unexplored area with a huge potential set of benefits. The creation of a standard system of interaction between sender and receiver will greatly simplify any efforts to create compatible programs using TRoR. To that end, I propose the following standard packet structure:
<two-character packet type code>:;<message contents>
Please note that any non-semicolon character can be placed between the colon and the semicolon by either end, which MAY be used for any purpose, but standard-compliant TRoR programs SHALL NOT malfunction if any characters are present there. This basically means that to get the message contents, a shell.match(message, ";(.*)") is suggested.
Here is a list of the two-character packet codes and their uses:
TW - carries written text to the client as the message contents.
TC - sets the cursor position on the client screen, format: "<x>,<y>".
TG - gets the cursor position from the client.
TD - gets the screen size from the client.
TI - used to return information to the server, such as screen size, cursor position, or color/non-color.
TE - used to clear the client screen.
TL - clears the current line of the client screen.
TS - scrolls the client screen.
TB - sets the cursor blink, using string literals "true" and "false"
TF - sets the foreground color.
TK - sets the background color.
TA - gets the value of term.isColor() from the client.
So, an example packet exchange might look like:
1: TD:;nil
2: TI:;51,19
1: TA:;nil
2: TI:;true
1: TC:;1,1
1: TF:;16
1: TK:;32768
1: TW:;CraftOS 1.4
1: TC:;1,2
1: TW:;>
Here is a packet type/name translation table, for more human-readable handling of these packets, which we will be using in our example implementation:
local packetConversion = {
textWrite = "TW",
textCursorPos = "TC",
textGetCursorPos = "TG",
textGetSize = "TD",
textInfo = "TI",
textClear = "TE",
textClearLine = "TL",
textScroll = "TS",
textBlink = "TB",
textColor = "TF",
textBackground = "TK",
textIsColor = "TA",
TW = "textWrite",
TC = "textCursorPos",
TG = "textGetCursorPos",
TD = "textGetSize",
TI = "textInfo",
TE = "textClear",
TL = "textClearLine",
TS = "textScroll",
TB = "textBlink",
TF = "textColor",
TK = "textBackground",
TA = "textIsColor",
}
So, that's basically the end of the proposed standard, on to the example implementation:
And here are a couple of utility functions that we'll use for easier handling of the packet format:
local function send(id, type, message)
return rednet.send(id, packetConversion[type]..":;"..message)
end
local function awaitResponse(id, time)
id = tonumber(id)
local listenTimeOut = nil
local messRecv = false
if time then listenTimeOut = os.startTimer(time) end
while not messRecv do
local event, p1, p2 = os.pullEvent()
if event == "timer" and p1 == listenTimeOut then
return false
elseif event == "rednet_message" then
sender, message = p1, p2
if id == sender and message then
if packetConversion[string.sub(message, 1, 2)] then packetType = packetConversion[string.sub(message, 1, 2)] end
message = string.match(message, ";(.*)")
messRecv = true
end
end
end
return packetType, message
end
Now, to redirect terminal output to the client, we will need to construct a terminal redirect table with all of the normal term functions, except that it sends them to the client rather than outputting anything on the screen. Here is a function that constructs a new redirect table. The function is passed a computer ID and constructs a redirect target that sends all of the term commands to that computer ID. Note that it references the two functions above, so if using this table, be sure to include those functions above this one.
local function textRedirect (id)
local textTable = {}
textTable.id = id
textTable.write = function(text)
return send(textTable.id, "textWrite", text)
end
textTable.clear = function()
return send(textTable.id, "textClear", "nil")
end
textTable.clearLine = function()
return send(textTable.id, "textClearLine", "nil")
end
textTable.getCursorPos = function()
if send(textTable.id, "textGetCursorPos", "nil") then
local pType, message = awaitResponse(textTable.id, 2)
if pType and pType == "textInfo" then
local x, y = string.match(message, "(%d+),(%d+)")
return tonumber(x), tonumber(y)
end
else return false end
end
textTable.setCursorPos = function(x, y)
return send(textTable.id, "textCursorPos", math.floor(x)..","..math.floor(y))
end
textTable.setCursorBlink = function(B)/>
if b then
return send(textTable.id, "textBlink", "true")
else
return send(textTable.id, "textBlink", "false")
end
end
textTable.getSize = function()
if send(textTable.id, "textGetSize", "nil") then
local pType, message = awaitResponse(textTable.id, 2)
if pType and pType == "textInfo" then
local x, y = string.match(message, "(%d+),(%d+)")
return tonumber(x), tonumber(y)
end
else return false end
end
textTable.scroll = function(lines)
return send(textTable.id, "textScroll", lines)
end
textTable.isColor = function()
if send(textTable.id, "textIsColor", "nil") then
local pType, message = awaitResponse(textTable.id, 2)
if pType and pType == "textInfo" then
if message == "true" then
return true
end
end
end
return false
end
textTable.isColour = textTable.isColor
textTable.setTextColor = function(color)
return send(textTable.id, "textColor", tostring(color))
end
textTable.setTextColour = textTable.setTextColor
textTable.setBackgroundColor = function(color)
return send(textTable.id, "textBackground", tostring(color))
end
textTable.setBackgroundColour = textTable.setBackgroundColor
return textTable
end
So, to handle these packets on the client end, we have a simple function that takes slightly-processed information about these packets and responds appropriately. It takes the server computer ID, the human-readable packet type, and the value sent with it as parameters. Note that this also uses the send() helper function above.
local function processText(conn, pType, value)
if not pType then return false end
if pType == "textWrite" and value then
term.write(value)
elseif pType == "textClear" then
term.clear()
elseif pType == "textClearLine" then
term.clearLine()
elseif pType == "textGetCursorPos" then
local x, y = term.getCursorPos()
send(conn, "textInfo", math.floor(x)..","..math.floor(y))
elseif pType == "textCursorPos" then
local x, y = string.match(value, "(%d+),(%d+)")
term.setCursorPos(tonumber(x), tonumber(y))
elseif pType == "textBlink" then
if value == "true" then
term.setCursorBlink(true)
else
term.setCursorBlink(false)
end
elseif pType == "textGetSize" then
x, y = term.getSize()
send(conn, "textInfo", x..","..y)
elseif pType == "textScroll" and value then
term.scroll(tonumber(value))
elseif pType == "textIsColor" then
send(conn, "textInfo", tostring(term.isColor()))
elseif pType == "textColor" and value then
value = tonumber(value)
if (value == 1 or value == 32768) or term.isColor() then
term.setTextColor(value)
end
elseif pType == "textBackground" and value then
value = tonumber(value)
if (value == 1 or value == 32768) or term.isColor() then
term.setBackgroundColor(value)
end
end
return
end
Here's a stub of a processing loop for receiving those packets. Note that this references the packetConversion table above.
while true do
event = {os.pullEvent()}
if event[1] == "rednet_message" then
if packetConversion[string.sub(event[3], 1, 2)] then
packetType = packetConversion[string.sub(event[3], 1, 2)]
message = string.match(event[3], ";(.*)")
processText(serverNum, packetType, message)
end
end
end
So, that's the proposed TRoR standard and the example implementation, which are used in my net shell program (nsh), to great effect. I propose that this TRoR specification be adopted as a semi-formal standard, and would like to request serious commentary on the standard, but not so much the example implementation (as any standard-compliant program would be free to implement it in its own way).