This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
streff84's profile picture

Rednet - passing a table from client to client - question (still crashing)

Started by streff84, 30 April 2012 - 10:26 AM
streff84 #1
Posted 30 April 2012 - 12:26 PM
Hey all, looking for a little pointer in how to pass a table across Rednet, and have the recipient be able to parse that table, and read specific items from it as variables…

Been messing around in CC for a few days now, and managed to hack together some scripts to control my turtle remotely, mainly by modifying other scripts i've found on here*.
Thus far, I have the Aware API installed, and a script to execute turtle commands running on the turtle, which works great. The command and control script runs on the computer terminal, and so far can succesfully pass instructions to the client.

So, to my question - I'm attempting to send a message from the turtle to the computer at 6 points in a sequence, and the computer will print those 6 messages before breaking back to it's previous menu. At the moment, whenever the aware API moves, it broadcasts it's location and other data, which is being recieved by the terminal, and kicking off the 6 recieve events with the wrong data, so I'm attempting to filter the data as it arrives, and only react to the data which is of the appropriate class, as defined by the first variable in the table i'm trying to send below - class "Z" should trigger the 6 print events, any other data should not.


rednet.send(ID, {"Z","some message", anothervariable})

Currently the result is that the terminal sits and waits for 6 events which never come, and the turtle crashes with the error "Rednet:330 Attempt to index ? (a nil value)"

I can alter the format of the send command so that it appears to send, but I can't seem to make the client read the resultant message.. i've looked at the way Biolisten seems to do it, and can't quite figure out the use of the textutils.serialise function it seems to call…

So it's 2 parts really, first, how to safely send a table containing 2 or 3 elements within a rednet.send() command, and then how to read that table from a variable again..

I get the feeling i'm missing something basic, but i've been at it a while now.. i'm fairly new to LUA, and it's been a few years since I did any real code work (outside of html / css type stuff), so i'm a little rusty.

Snippets of relevent code below -
SpoilerTurtle code:

elseif message == "repeat" then	   -- turtle is listening for a message, "repeat" is sent by terminal
i = 1
	repeat
	rednet.send(ID, {"Z","Going to site", i})	  -- send message with progress update, Z denotes category, then message, then the iteration it is currently doing
	aware.goto(site)									-- go to site (defined elsewhere, works fine when i take out all the messaging stuff)
	shell.run("Excavate2 ", "9")				  -- dig dig dig
	rednet.send(ID, {"Z","Going home", i})
	aware.gohome()
	aware.turnleft()
	aware.dump()
	aware.turnright()
	site[3] = site[3]-5
	i = i + 1
	until i > 3

terminal code - Terminal sends message to turtle to say 'execute "repeat" command', and waits for 6 responses -

info = "repeat"  -- turtle is expecting this input. works ok.
rednet.send(ID, info)
i = 1
	repeat
	local ID, ackmsg = rednet.receive()		   -- store return message as "ackmsg"
	category = ackmsg[1]							  -- take 1st element of table in "ackmsg" and store as "category"
	if category == "Z" then							-- if right category, print it, if wrong, ignore it
	print(ackmsg)
	i = i + 1
	end
	until i > 6   										   -- do it 6 times
	print("Done")	
	sleep(7)


*Credits:
SpoilerCredit to Squareman for CCC, which I butchered mercilessly to make my version, but kept the menu system, because it's pretty cool. Also, major credit to Biopsy for the Aware API which I use pretty heavily.
Onionnion #2
Posted 30 April 2012 - 02:34 PM
Yes there is an easy way to send tables via rednet and that's through a textutil with which you insert a table to its parameter. I cannot remember the name as I am away from a source of the name (wiki needs work) but it should be in the textutil help file. Enter 'help textutil' and see what comes up.
streff84 #3
Posted 30 April 2012 - 03:33 PM
I've taken a look at the documentation for textutils.serialize() / unserialize(), but it's not very helpful -

with my send code as


local ackmsg = textutils.serialize({os.getComputerID(), "Z", "Going to Site", i})
    rednet.send(id, ackmsg)

and my receive as


local ID, ackmsg = rednet.receive()
    local fackmsg = textutils.unserialize(ackmsg)
    local turtID = fackmsg[1]
    local category = fackmsg[2]
    local data = fackmsg[3]

i get an error of


textutils:143: textutils:141: [string "serialize"]:1: <'eof>' expected
streff84 #4
Posted 30 April 2012 - 03:50 PM
edit: not quite solved. Seems to ignore some messages which should have triggered the 'print' command, and then crashes with the same 'eof' error.

send code:


elseif message == "repeat" then -- follow on from if statement further up in code, ignore for now
	i = 1							  -- set loop counter
	repeat						   -- start loop
	local ackmsg = textutils.serialize({os.getComputerID(), "Z", "Going to Site", i})	-- put the table between the () into a string with labels, and put it in the variable 'ackmsg'
	rednet.send(id, ackmsg)		   -- send it out
	aware.goto(site)		 -- go to site
	local ackmsg = textutils.serialize({os.getComputerID(), "Z", "Starting Dig", i})	  -- repeat as above...
	rednet.send(id, ackmsg)
	shell.run("Excavate2 ", "9")
	local ackmsg = textutils.serialize({os.getComputerID(), "Z", "Going Home", i})
	rednet.send(id, ackmsg)
	aware.gohome()
	aware.turnleft()
	aware.dump()
	aware.turnright()
	site[3] = site[3]-5
	i = i + 1
	until i > 3

receive code

i = 1
	repeat	   -- start loop, counter set to 1
	local ID, ackmsg = rednet.receive()   -- store incoming id in "ID", and message in "ackmsg"
	local fackmsg = textutils.unserialize(ackmsg)   -- take the string from "ackmsg" and put it in the table "fackmsg" (formatted ackmsg)
	local turtID = fackmsg[1]		 -- read the first table element, and store it as turtID
	local category = fackmsg[2]	-- store 2nd element as category
	local data = fackmsg[3]		   -- store 3rd as message
	if category == "Z" then	 -- if the category matches "Z"
	print(data)			-- print the data, which should read "Going to Site" or as appropriate
	i = i + 1		-- increment the loop conter.. etc
	else
	end
	until i > 9	
	print("Done")  
cant_delete_account #5
Posted 30 April 2012 - 06:55 PM
I just send 1 message which is table.maxn(TABLENAME) to a computer, then I make a for loop on the receiving one, which receives, sleeps for 0.2 seconds, and does that for the length of the table, and on the sending computer it will send each "part" of the table, then I have this on the receiving computer too:

all of the receiving code, etc would be up here ^
local TABLENAME = {}
table.insert(TABLENAME,YOURMSGVARIABLE)
streff84 #6
Posted 30 April 2012 - 09:19 PM
i currently use a 4 part transmission to update the 'site' coordinates on the turtle, I had tried to do it that way, but when the turtle moves, it spams 2 seperate coordinate messages using aware.pushpos(id, class, {x,y,z,o}). so i'd like my messages to keep the same format - that way I can hopefully filter out the aware.pushpos messages, and only read the ones intended to carry the progress information.. once it's working I want to expand on it a bit..
Luanub #7
Posted 30 April 2012 - 09:53 PM
They way I do this is to convert the table to a string, send the string then split the string back into a table. I use a CSV style of format on the string then split at the designated separator, it works flawlessly for me. If you want some examples on how to do it let me know.
streff84 #8
Posted 30 April 2012 - 10:36 PM
i think this is what the textutils.serialize() and unserialize commands do, basically turn a table into a string with labels on it denoting the table entry number, i think i'm just getting some of the 'unserialise' part wrong. It does seem to work ok on some messages, but seems to be triggered to crash by some of the movement spam that aware pushes out. I'm hoping to have some time to work on it more tomorrow, so will post any progress.

I'm also running into a problem when the turtle moves out of range, it's messages are not being recieved by the terminal, so i'm restricting it to only sending status updates at one point during the trip (the dropoff, when i know it'll be in range) - and i'm changing the numbered loop to a condtional loop, so that the reciever doesnt wait for messages that are sent out of range, so if the category = "Z" then break, if category = anything else, keep reading the inputs and comparing them.. we shall see.

if i can get this working, i'm hoping to be able to rewrite the transmission code to use broadcasts in the same format as aware.pushpos, which may then be relayed across some servers… which should overcome the range issue.
streff84 #9
Posted 01 May 2012 - 12:17 PM
Would appreciate an example if you have one handy - I think i'm missing something in the example i'm working from (biolisten / biomine / aware api pushpos function)
Luanub #10
Posted 01 May 2012 - 01:07 PM
So I never really tried the textutils functions that do this until tonight and I played around with them some. They are very handy, they do exactly what I was doing with my functions only keep my script much cleaner. The only tricky thing I found is the way it formats the string {[1]="string",} seems to cause a couple issues but its fairly easy to work around.

The following will need the strings to be formatted in csv sString = ("a, b, c")


to split the string into a table:

function pattern(text,pattern,start)
return string.sub(text,string.find(text,pattern,start)) end

function split(string)
local sep = {}
local done = false
local count,prevVars,tmp = 0,0,0
string = string..",|"
while not done do
  count = count + 1
  tmp = pattern(string,"[^,]+",count+prevVars)
  if tmp == "|" then done = true return sep end
  prevVars = prevVars + tmp:len()
  table.insert(sep,count,tmp)
end
return sep
end

tTable = split(sString) -- to split the string

======================================================================

To turn the table into a string again

local num = table.maxn(tTable)
local sString = tTable[1]
local a = num - 1
local b = 2
for x=1, a do
   sString = sString..","..tTable[b]
   b = b + 1
end

I have to say I like the textutils function as they keep my script allot cleaner. I've already converted my script to use them, but if this works better then I would use it. Go with what works for what you're trying to achieve, i've used this exact code to send file contents from one computer to another.
Edited on 01 May 2012 - 11:23 AM
streff84 #11
Posted 02 May 2012 - 08:19 PM
Ok, i've managed to get the turtle to be able to report it's digsite location to the client on demand, using the 'serialize' command -


if message == "report" then
rednet.send(id, "Site: "..textutils.serialize(site))

and it seems to work ok, so i'm gonna keep on looking into what i'm doing wrong with it on the parts of the script.

I like the look of your workaround, and if I can't get this to work, I may need to try and implement it, but it will still run in to the same problem - that whenever the turtle moves, the position update spam kicks off the recieve functions in the terminal, which can't interpret the data in a meaningful way (in this instance, interpret it and decide to discard it, and listen some more…)
MysticT #12
Posted 02 May 2012 - 08:51 PM
A good way to discard any "garbage" message you get is using some kind of keyword before the message. When you receive a message you check if the keyword is there, if it is you process the message, else discard it.
Example:
Sender

rednet.send(id, "Report: "..textutils.serialize(yourDataVariable))
Receiver

while true do
  local id, msg = rednet.receive()
  if id == trustedId then -- optional check for sender id
    local sData = string.match(msg, "Report: (.+)")
    if sData then
	  -- the message is correct and contains data
	  local data = textutils.unserialize(sData)
	  -- now do whatever you need with the data
    end
  end
end