I am trying to read the stream from Arena with my python application for some realtime usage.
I had a look at the PacketClient sample from NatNetSDK and tried to "convert" it to python to read the data. I managed to bind a socket and get the packets into python. But it seems to be messed up in some way. Here is what one packet looks like:
First I thought the packets are kind of compressed as I found an "unpack" function in packetclient.ccp
But meanwhile I think, that some kind of data formation for network transfer was used ("host to network"), but I cannot find any hint in the sample files.
So here is what I am doing in python to get a packet:
Quen, I too want to use Python to intercept NatNet content. Have been unsuccessful so far. My C++ skills are minimal at best. I understand fair amount of Python. I can use the two programs listed below {socketSend.py and socketRec.py} to send/receive packets to the machine called 172.16.93.245. {socketSend.py is running on machine A(same machine as trackingTools). socketRec.py is running on machine B, with IP of 172.16.93.245}. I've looked over your posted script and have tried it. It 'sits' there apparently looking for data, but no response from the stream. A couple questions:
* I see some reference to multi-use of a socket in your script. Why was that necessary?
* Your line "multicastAdd="xxx"", what machines IP should be here?, I'm using the receiving machines IP. Why do you need to define it at all?
* Your line "data, addr = s.recvfrom(10240)"; what does the 10240 number reference?
* How should the tracking tools network options be set? I've got Network Options/Type set to Unicast. Network Options/Data Port set to 1511. Network Interface Selection/Local Interface set to Preferred. Network Interface Selection/Multicast Interface set to 172.16.93.245.
* Maybe your running your script and the tracking tools on the same machine. Is that true? What would a script look like that is running on a remote machine 'b', but receiving data from the tracking tools machine 'a'?
* And finally, what did you find that 'unpacks' the apparent data stream. Was it pickled?
socketSend.py
import socket
while 1:
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
data = raw_input('Enter something to transmit\n')
sock.sendto(data,('172.16.93.245',1511))
sock.close()
socketRec.py
print ""
print "Receive Socket"
import socket
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #Defining sock module
sock.bind(('',1511)) #Define port to use.
while 1 :
data,address = sock.recvfrom(1511)
print data
YourProgramModified4MyEnvironment.py
import socket
import struct
# values
ip = "127.0.0.1"
port = 1511
multicastAdd = "172.16.93.245" # receiving machine IP and where this script is running
# pack multicast address
mreq = struct.pack('4sl', socket.inet_aton(multicastAdd), socket.INADDR_ANY)
# create socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# allow multiple use of the same socket
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind socket
s.bind((ip,port))
# add socket to multicast group
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
# recieve data
data, addr = s.recvfrom(10240)
print data
Couple things make this do-able:
* Sample file of python to read the packet string
* Correct setup of tracking tools streaming data options
* The packet string is a binary package. The python module 'struct' can be used to unpack it if you know the formula.
* The formula is imbedded in PacketClient.cpp
A working python script is below assuming you have the NaturalPoint streaming engine set to output to Data Port 1511 and it's multicast Interface is set at 239.255.42.99. It prints the result and stores the packet in a file called 'optiOutput.txt'. Now you can play around with the data without need for the tracking tools running.
Look in PacketClient.cpp with a text editor down about line 488. This is where the unpack function is being done in their C++ example. Notice that pData is their var being passed from up above {the raw data}. It's quickly set to the *ptr {pointer}. This *ptr and it's subsequent incrementation is how your going to unravel the twisted story. A couple lines down you see 'memcpy(&MessageID, ptr, 2); ptr += 2;' and the line above it 'int MessageID = 0;' The former line is defining an integer variable. Believe the next is setting memory space up for the MessageID info, but it also increments the ptr by 2 at the end via a second command. The increment is important because it gives us the key for what the formula needs to be in the python struct sequence.
Before we go any further, please reference: http://docs.python.org/library/struct.html. In there, you will see a section on Format Characters. Each format character is represented by a unique number of bytes. Please notice that 'h' is a short integer form with size of 2 bytes. That's the same number we just discovered above in the Unpack function of packetClient.cpp.
If you go down to line 504, you'll notice another incrementation of ptr by 2 and the line above it referencing an int {integer} form. Sounds like another h format character to me.
Down a couple more, you see the first increment of 4 and the int form again. Looking at the struct ref. you'll see there is a 4 byte integer reference with format of 'i'. You'll use this i format code as the next character in the struct format.
At some point, you'll run into a float type C++ form. The corresponding struct format character for a 4 byte float is 'f'. So, going through the .cpp code and placing the appropriate format codes together, you may end up with a struct statement like:
struct.unpack('hhiiiiifffffffifffffffffiiiffffifffffffifffffffffiiiffffiicccc',data) #where data is the raw data.
This particular one worked for me where I have 2 rigid bodies and 3 markers/body. Yours could be significantly different, but the idea should be similar.
A very short python code group that let me see the test data collected earlier turned out to look like:
import struct
f=open('optiOutput.txt', 'r')
data=f.read()
print 'data length ',len(data)
dataOut=struct.unpack('hhiiiiifffffffifffffffffiiiffffifffffffifffffffffiiiffffiicccc',data)
print 'dataOut ',dataOut
I found knowning the raw data length very helpful. Your going to make mistakes getting the format correct. Take just a couple of them at a time. I put them in a spread sheet so I could keep track of the bytes being consummed by each format character. The remaining, you can pad with 'c' {a byte count of 1}. Take the total data length, subtract what you've formatted, and pad the rest with c to make the same byte count as the packet. At some point, you'll get through the mess.