Posted 25 February 2012 - 04:50 PM
I'll leave the following intact for educational purposes.
But there is a much easier way to do it and if you want to skip to that easier solution, then scroll down to the fourth post.
Original Post:
I've been playing around with the wireless modems a bit and wanted to be able to send a command to the receiving computer which it then should execute.
But I didn't just want to map specific rednet-messages to specific functions on the receiving end.
I rather wanted to be able to send any command in string form to the receiver, have him interpret the command with all its arguments correctly from the string and finally (provided the command is a valid one for the receiver) execute that command.
This is only a proof-of-concept, if you will, and by no means fool-proof.
But at the moment it should work for all global functions + the shell API (which isn't in the global table).
The two core functions are getFunc( _sFuncCall ) which will return a function for the given string and getArgs( _sFuncCall ) which will return all the arguments in their proper type.
The following code contains both of these functions, along with two helper functions for string-trimming and -splitting, along with some example code for how you could make use of the two core functions:
USAGE:
What values the two core functions expect as input and what they return is pretty much explained within the comments of the code itself.
If you want to test the example code from above, do the following:
Note that you have to escape double quotes if they are part of the command that you want to send!
Review the last send example for that.
Ok, that's all for now.
I haven't tested it extensively, as I was first trying to get the principal idea working.
So if you would like to give it a try and give some constructive feedback, I'd very much appreciate that. :(/>/>
EDIT: Fixed outputting of nil by adding tostring() within the prints.
But there is a much easier way to do it and if you want to skip to that easier solution, then scroll down to the fourth post.
Original Post:
I've been playing around with the wireless modems a bit and wanted to be able to send a command to the receiving computer which it then should execute.
But I didn't just want to map specific rednet-messages to specific functions on the receiving end.
I rather wanted to be able to send any command in string form to the receiver, have him interpret the command with all its arguments correctly from the string and finally (provided the command is a valid one for the receiver) execute that command.
This is only a proof-of-concept, if you will, and by no means fool-proof.
But at the moment it should work for all global functions + the shell API (which isn't in the global table).
The two core functions are getFunc( _sFuncCall ) which will return a function for the given string and getArgs( _sFuncCall ) which will return all the arguments in their proper type.
The following code contains both of these functions, along with two helper functions for string-trimming and -splitting, along with some example code for how you could make use of the two core functions:
Spoiler
--[[ HELPER FUNCTIONS ]]
-- Removes leading and trailing spaces.
function trim( s )
return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end
-- explode(string, seperator)
function explode(p, d)
local t, ll
t={}
ll=0
if(#p == 1) then return {p} end
while true do
l=string.find(p,d,ll,true) -- find the next d in the string
if l~=nil then -- if "not not" found then..
table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array.
ll=l+1 -- save just after where we found it for searching next time.
else
table.insert(t, string.sub(p,ll)) -- Save what's left in our array.
break -- Break at end, as it should be, according to the lua manual.
end
end
return t
end
--[[ CORE FUNCTIONS ]]
-- Retrieves arguments from a string within brackets and converts them according to these rules:
-- "house" => becomes the string 'house'
-- true => becomes the boolean 'true'
-- false => becomes the boolean 'false'
-- 123 => becomes the number '123'
-- Returns:
-- true and a table with the arguments, if successful.
-- false and nil, if not successful.
function getArgs( _sFuncCall )
local nStart, nEnd = string.find( _sFuncCall, "%(.*%)" ) -- Find content between brackets.
nStart = nStart + 1 -- Adjust starting position.
nEnd = nEnd - 1 -- Adjust ending position.
local content = string.sub( _sFuncCall, nStart, nEnd ) -- Get content between brackets.
content = trim( content )
if #content < 1 then return true, nil end -- There are no arguments, so we can stop here already and return nil.
content = content.."" -- Temporary fix for the string.find() problem.
content = explode( content, "," ) -- Separate contents at the commas.
-- Replace table string values with the types they actually represent, e.g. replace a string 'true' with a boolean 'true', etc.
for i = 1, #content do
if content[i] ~= nil then
content[i] = trim( content[i] )
if string.lower( content[i] ) == "true" then
-- Boolean TRUE
content[i] = true
elseif string.lower( content[i] ) == "false" then
-- Boolean FALSE
content[i] = false
elseif tonumber( content[i] ) ~= nil then
-- NUMBER
content[i] = tonumber( content[i] )
else
-- STRING
-- Assume the string is encased within double quotes.
local nStart, nEnd = string.find( content[i], "%\".*%\"" ) -- Find content between double quotes.
if nStart == nil then
return false, "Error at argument '"..content[i].."'\nOnly strings, booleans and numbers are supported (yet)."
end
nStart = nStart + 1 -- Adjust starting position.
nEnd = nEnd - 1 -- Adjust ending position.
content[i] = string.sub( content[i], nStart, nEnd ) -- Get content between double quotes.
content[i] = content[i].."" -- Temporary fix for the string.find() problem.
end
end
end
return true, content
end
-- _sFuncCall - Command-Call in string form.
-- Returns:
-- The function to the given comand, if successful.
-- nil, if not successful.
function getFunc( _sFuncCall )
local tSub = explode( _sFuncCall, "." )
local func = tSub[1]
if func == "shell" then
func = shell -- 'shell' isn't in the global table, it's actually a program within which THIS program has been executed from.
else
func = _G[ func ]
end
if type( func ) == "table" then
local nStart, nEnd = string.find( tSub[2], "%(.*%)" ) -- Find content between brackets.
if nStart == nil then return nil end -- There are no brackets, so we don't treat this as a function call.
tSub[2] = string.sub( tSub[2], 1, nStart - 1 ) -- Everything, excluding the brackets and its contents.
if type( func[ tSub[2] ] ) == "function" then
return func[ tSub[2] ]
else
return nil
end
end
end
--[[ EXAMPLE ON HOW TO MAKE USE OF IT ]]
local modemSide = "right"
term.setCursorPos( 1, 1 )
term.clear()
print("Listening...")
rednet.open( modemSide )
while true do
local sEvent, p1, p2 = os.pullEvent()
if sEvent == "rednet_message" then
print( "Message from Computer #"..p1 )
print( "Command:\n"..p2 )
local func = getFunc( p2 )
if type( func ) == "function" then
local ok, tArgs = getArgs( p2 )
if ok and tArgs ~= nil then
print( "Result:\n"..tostring( func( unpack( tArgs ) ) ) )
elseif ok and tArgs == nil then
print( "Result:\n"..tostring( func() ) )
else
print( tArgs )
end
print()
end
end
if sEvent == "char" and p1 == "q" then break end
if sEvent == "char" and p1 == "c" then term.setCursorPos( 1, 1 ) term.clear() print("Listening...") end
end
rednet.close( modemSide )
print("Finished")
USAGE:
What values the two core functions expect as input and what they return is pretty much explained within the comments of the code itself.
If you want to test the example code from above, do the following:
- Create a computer or turtle with a wireless modem.
- Modify the variable modemSide in the code above to point to the side where the modem is on the receiving computer/turtle.
- Start the program on the receiving computer/turtle.
rednet.send( 2, "os.time()" )
This should return the time on computer #2 (on which the example program from above would be running).rednet.send( 2, "redstone.setOutput(\"left\", true)" )
Turns on the left redstone output of the receiver.Note that you have to escape double quotes if they are part of the command that you want to send!
Review the last send example for that.
Ok, that's all for now.
I haven't tested it extensively, as I was first trying to get the principal idea working.
So if you would like to give it a try and give some constructive feedback, I'd very much appreciate that. :(/>/>
EDIT: Fixed outputting of nil by adding tostring() within the prints.
Edited on 25 February 2012 - 07:42 PM