Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting to a UDP data stream with Mathematica

I have an app on my iPhone called iSeismometer which reads the iPhone's accelerometers and acts as a server which streams this data via UDP (I can set the IP address and port number). The question is how to read this data stream with Mathematica? Apparently, Dreeves has been looking into this 12 years ago, so I imagine something must have happened in the meantime.

Update
I got two great answers so far; one from WReach and one from Mark McClure. Both are using JLink to get at the data. This seems like a fine approach. However, I was reminded of some work I did on the WII balance board. Using a few free programs (GlovePIE and PPJoy) I got this bluetooth peripheral to appear as a joystick to Windows, and therefore also to Mathematica (via ControllerState). Of course, bluetooth and UDP are quite different, but could something along the same lines be made to work too?

like image 935
Sjoerd C. de Vries Avatar asked Apr 29 '11 21:04

Sjoerd C. de Vries


2 Answers

JLink is definitely the way to go. I prefer to keep my Java code and my Mathematica code separate by compiling a Java programwhich I then call from Mathematica. I set up a Notebook and companion Java program that you can grab here: http://facstaff.unca.edu/mcmcclur/UDPFiles.tar.gz

Here is the essential Mathematica code:

Needs["JLink`"];
InstallJava[];
AddToClassPath[NotebookDirectory[]];
udpReader = JavaNew["myClient"];
i = 0;
While[True && i++ < 100,
  Print[udpReader@udpReadOne[10552]]]

The updReader class is defined by the following Java code.

// A simple UDP client to read from iseismometer:
// http://www.iseismometer.com/
// You can run this from the command line via "java myClient"
// to check that your iseismometer setup is correct or you can
// call the the udpReadOne method from another program.

import java.io.*;
import java.net.*;
import java.util.*;

public class myClient {
    public static void main() throws IOException {
        DatagramSocket socket = new DatagramSocket(10552);
        byte[] buffer = new byte[500];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        while(true) {
            socket.receive(packet);
            String received = new String(packet.getData(), 0, packet.getLength());
            System.out.println(received);
        }
    }

    public static String udpReadOne(int port) throws IOException {
        DatagramSocket socket = new DatagramSocket(port);
        byte[] buffer = new byte[100];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);
        String received = new String(packet.getData(), 0, packet.getLength());
        socket.close();
        return received;
    }
}

Note that you can use the main method of the myClient class to check that your setup is working without Mathematica, essentially taking one potential issue out of the loop.

like image 129
Mark McClure Avatar answered Oct 24 '22 04:10

Mark McClure


Assuming the set-up discussed in a blog entry on the iSeismometer web site, a couple of options come to mind.

Import

The first option would be to use an external program to capture the packets, and then use Import to bring in the results, e.g.

Import["!someexternalprog", "Lines"]

Alas, the Python program mentioned in the blog post will not work well here since it runs in an endless loop that must be manually terminated. The Import approach would only work if that program were modified to stop after a fixed number of packets or a time limit or something.

JLink

An alternate approach can be implemented without leaving the comfy Mathematica environment by using JLink. Well, perhaps it is a stretch to say that we are staying within Mathematica since a fair amount of funny-looking Java code is mixed in with the Mathematica code. Nevertheless, it does illustrate the utility of the built-in Java distribution that ships with every copy of Mathematica:

Needs["JLink`"]
LoadJavaClass["java.util.Arrays"];

ClearAll@ListenToISeismometer
ListenToISeismometer[port_] :=
  JavaBlock@Module[{socket, packet, listen, record = Null, listening = True}
  , packet = JavaNew["java.net.DatagramPacket", JavaNew["[B", 1024], 1024]
  ; listen[] :=
      If[$Failed =!= Quiet[socket@receive[packet], Java::excptn]
      , record =
          JavaNew[
            "java.lang.String"
          , java`util`Arrays`copyOfRange @@ packet /@ {getData[], getOffset[], getLength[]}
          ]@toString[] // Sow
      ]
  ; Row[{Button["Stop", listening = False], Dynamic[record]}, "  "] // PrintTemporary
  ; AbortProtect[
      socket = JavaNew["java.net.DatagramSocket", port]
    ; socket@setSoTimeout[1000]
    ; Reap[While[listening, listen[]]; socket@close[]][[2, 1]]
    ]
  ]

Some shortcuts have been taken with respect to exception handling, packet decoding and the like in order to keep this example at a manageable length.

ListenToISeismometer needs to be given the UDP port number to listen upon. Let's use the same port as in the blog post, 10552:

In[33]:= data = ListenToISeismometer[10552];

stop button

The function will listen to all UDP events on that port until told to stop. A button is presented for this purpose, with each packet flashing by along side as received. When the button is pressed, the function returns a list of the packets received:

In[34]:= data // Column
Out[34]= 1,83575.099,0.029,0.044,0.094
         1,83575.781,0.056,0.033,0.099
         1,83575.924,0.047,0.054,0.094
         1,83575.613,0.096,0.092,0.057
         1,83575.748,0.073,0.049,0.061
         1,83575.577,0.008,0.089,0.020
         ...

JLink makes this possible, but there is no escaping the fact that the use of JLink requires a working knowledge of Java.

like image 4
WReach Avatar answered Oct 24 '22 04:10

WReach