Clarification on the connecting jMonkey to native Android code

First of all, let me thank all of you for the wonderful jMonkey SDK and the work that has been done on extending jMonkey to Android. I have found the whole system a joy to use and learn.

I am confused on how connecting a jMonkey application to native Android code should be implemented in an expected manor. From the main Android page (that gets linked to countless times in this forum), I read this:

As an example, if you want to use the phones camera as an image input stream for a texture, you can create e.g. the AppState that manages the image and makes it available to the application inside the main project (no android code is needed). Then in the android part of the code you make a connection to the camera and update the image in the AppState.

The concept of this makes complete sense to me. It is essentially saying that jMonkey shouldn’t care about where the data comes from and that you should only connect native Android code to a “middle man” that connects the Android code to the main code. However, I am confused by the implementation of this. It is my understanding that AppStates are for when you want the jMonkey game to jump between different states like a main menu, a paused state, or a game running state. Why is this “middle man” class an AppState?

My particular project involves retrieving live GPS data and saving it while the game is going on while also having a character on the screen update based on the user’s location. In this case, it seems to me that the GPS reading needs to either run in its own thread or be used in a callback fashion such as updating the game screen when a new GPS reading comes in. If this is the case, and the “middle man” class is an AppState, doesn’t that break? From what I understand, only one AppState can be running at a time and in the main thread you attach/detach the AppStates as appropriate. Since I want to be gather GPS data constantly, it seems like making the “middle man” class an AppState defeats the purpose (but I also realize I am probably WAY off base in my way of thinking).

Any help or insight that could be provided would be greatly appreciated.

P-Worm

Anyway, this is what I have right now concerning the GPS:

Main
[java]import com.jme3.app.SimpleApplication;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.system.AppSettings;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.CheckBoxStateChangedEvent;
import de.lessvoid.nifty.controls.Label;
import de.lessvoid.nifty.controls.ListBox;
import de.lessvoid.nifty.controls.TextField;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.SizeValue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication implements ScreenController {

private Nifty nifty;
private BlankState blankState;
private GraphicsState graphicsState;
private LocationStream locationStream;

// Used to test out label updating in GUI
private int switchScreenCounter;

// Flags for whether the program is logging data
boolean loggerRunning;

public static void main(String[] args) {
    Main app = new Main();
    Logger.getLogger("").setLevel(Level.SEVERE);

    app.setDisplayStatView(false);
    app.setShowSettings(false);
    AppSettings newSettings = new AppSettings(true);
    newSettings.setResolution(480, 854);
    newSettings.setFrameRate(60);
    app.setSettings(newSettings);
    app.start();
}

// Connections for GUI
public void bind(Nifty nifty, Screen screen) {
    System.out.println("bindFromMain( " + screen.getScreenId() + ")");
}

public void onStartScreen() {
    System.out.println("onStartScreen Main");
}

public void onEndScreen() {
    System.out.println("onEndScreen Main");
}

public void quit() {
    nifty.gotoScreen("end");
}

/*
 * GUI methods
 */
// Switch states and GUI screens
public void switchScreen(String screenName) {

    if (screenName.equals("graphics")) {
        System.out.println("Switching state to graphics");
        graphicsState.setEnabled(true);
        stateManager.detach(blankState);
        stateManager.attach(graphicsState);
        nifty.gotoScreen(screenName);
    } else {
        System.out.println("Switching state to blank state");
        graphicsState.setEnabled(false);
        stateManager.detach(graphicsState);
        stateManager.attach(blankState);
        nifty.gotoScreen(screenName);
    }
    
    switchScreenCounter++;
    String message;
    message = "Number of screen switches: " + Integer.toString(switchScreenCounter);
    message += " \nplus here's a bunch of \ntext to make sure that \nthe words rollover to \nnew lines when they need to.";
    if (screenName.equals("connection")) {
        updateLabel("checkConnOutput", message);
    }
}

// Changes a label in the GUI
private void updateLabel(String labelId, String message) {
    Label mLabel = nifty.getCurrentScreen().findNiftyControl(labelId, Label.class);
    mLabel.setText(message);
    // As Nifty currently stands, the width of the displayed text does not
    // dynamically change to accomadate larger strings than what is first
    // rendered.
    TextRenderer mRenderer = mLabel.getElement().getRenderer(TextRenderer.class);
    mLabel.setWidth(new SizeValue(mRenderer.getTextWidth() + "px"));
    // Update the panel
    mLabel.getElement().getParent().layoutElements();
    nifty.getCurrentScreen().layoutLayers();
}

// Add an item to the list box and remove the oldest item if the box is
// currently full
private void addItemToLogger(String newItem) {
    ListBox listBox = nifty.getCurrentScreen().findNiftyControl("loggerListBox", ListBox.class);
    int numItems = listBox.itemCount();
    // The list box can only handle 15 items. If the box is already full,
    // pop out the oldest item and add the newest item
    if (numItems == 15) {
        listBox.removeItemByIndex(0);
    }
    
    listBox.addItem("\t"+newItem);
}

// After the user presses the Check Connections button, run through
// connection tests
public void checkConnections() {
    System.out.println("Pressed check connections");
    updateLabel("checkConnOutput", "Button pushed!");
    updateLabel("internetSsidOutput", "Button pushed!");
    updateLabel("mocapSsidOutput", "Button pushed!");
    updateLabel("localIp", "Button pushed!");
    updateLabel("connectionStatus", "Button pushed!");
}

@NiftyEventSubscriber(id="gCheckBox")
public void gCheckBoxToggled(String id, CheckBoxStateChangedEvent event) {
    if (event.isChecked()) {
        updateLabel("checkConnOutput", "3G checkbox is checked!");
    } else {
        updateLabel("checkConnOutput", "3G checkbox is unchecked!");
    }
    
}
    
@NiftyEventSubscriber(id="wifiCheckBox")
public void wifiCheckBoxToggled(String id, CheckBoxStateChangedEvent event) {
    if (event.isChecked()) {
        updateLabel("checkConnOutput", "Wifi checkbox is checked!");
    } else {
        updateLabel("checkConnOutput", "Wifi checkbox is checked!");
    }
    
}
// Start capturing the mocap data
public void startCapturingData() {
    // Set the timer to 0
    timer.reset();
    
    loggerRunning = !loggerRunning;
    
    // Read in the name for the output file
    TextField filenameTextField = nifty.getCurrentScreen().findNiftyControl("filenameInput", TextField.class);
    String filename = filenameTextField.getText();
    updateLabel("connectedIpLabel", filename);
    updateLabel("gpsData1", "1");
    updateLabel("gpsData2", "2");
    updateLabel("gpsData3", "3");
    updateLabel("gpsData4", "4");
}

// Send an email with the data as an attachment
public void sendEmail() {
    // Read info from the email field
    TextField emailTextField = nifty.getCurrentScreen().findNiftyControl("emailEntry", TextField.class);
    String emailAddress = emailTextField.getText();
    TextField filenameTextField = nifty.getCurrentScreen().findNiftyControl("filenameInput", TextField.class);
    String filename = filenameTextField.getText();
    float timeInSec = timer.getTimeInSeconds();
    addItemToLogger(String.format("%.2f: ", timeInSec) + filename);
    updateLabel("connectedIpLabel", filename);
    updateLabel("gpsData1", "1");
    updateLabel("gpsData2", "2");
    updateLabel("gpsData3", "3");
    updateLabel("gpsData4", "4");
}

// Updates the live time logger
private void updateLoggerTimeDisplay() {
    float timeInSec = timer.getTimeInSeconds();
    String timeOutput;
    timeOutput = String.format("%.2f", timeInSec);
    updateLabel("elapsedTime", timeOutput);
}

@Override
public void simpleInitApp() {
    blankState = new BlankState(this);
    graphicsState = new GraphicsState(this);
    locationStream = new LocationStream(this);
    
    loggerRunning = false;

    // Setup the GUI
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
            inputManager,
            audioRenderer,
            guiViewPort);
    nifty = niftyDisplay.getNifty();
    nifty.fromXml("Interface/settingsScreens.xml", "connection", this);
    nifty.addXml("Interface/graphicsScreen.xml");
    guiViewPort.addProcessor(niftyDisplay);
    inputManager.setCursorVisible(true);
    System.out.println("Initialized Main");
    stateManager.attach(blankState);

}

/*
 * Override the main update loop
 */
@Override
public void simpleUpdate(float tpf) {
    // Only update the display if the timer is running and we are on that
    // particular screen
    if (loggerRunning && nifty.getCurrentScreen() == nifty.getScreen("logger")) {
        updateLoggerTimeDisplay();
    }
}

}[/java]

LocationStream which is my “middle man” class

[java] import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioRenderer;
import com.jme3.input.InputManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;

public class LocationStream extends AbstractAppState {

public SimpleApplication app;
private Node rootNode = new Node("Root Node");
private AssetManager assetManager;
private InputManager inputManager;
private AudioRenderer audioRenderer;
private ViewPort guiViewPort;
private AppStateManager stateManager;

// Will be continuously updated to the location of the device
Location location;

public LocationStream(SimpleApplication app) {
    this.assetManager = app.getAssetManager();
    this.inputManager = app.getInputManager();
    this.audioRenderer = app.getAudioRenderer();
    this.guiViewPort = app.getGuiViewPort();
    this.stateManager = app.getStateManager();

    location = new Location();
}

public Location getLocation() {
    return location;
}

public void setLocation(float longitude, float latitude, int altitude) {
    location.setLocation(longitude, latitude, altitude);
}

@Override
public void initialize(AppStateManager stateManager, Application app) {
    super.initialize(stateManager, app);
    this.app = (SimpleApplication)app;
    System.out.println("Initialized: app is "+this.app);
}

@Override
public void update(float tpf) {
    super.update(tpf);

    rootNode.updateLogicalState(tpf);
    rootNode.updateGeometricState();
}

}
[/java]

1 Like

Its an AppState because that is the easiest way to get a callback from the graphics update loop. The idea is to drain some queue or update the data in a locked manner to get the required data into the graphics update loop. Check the wiki on multithreading.

What I would do is create an app state in the Android project and add to your main project after super.onCreate has run. Then, when gps updates come in to the app state, queue them locally. When the engine calls the app state update method, you can put in some code that either moves the node or simply passed some data back to the main project.

The nice thing about doing it this way is that the app state will get called every update loop automatically by the engine, but it is defined in the Android side so you can have access to the Android specific class or jni interface to the GPS information.

Thank you both for your input. After doing a lot of reading in the wiki on Multithreading, Controls, and AppStates, I’m pretty sure I’m confused how a project should “look” in jMonkey. normen’s reply on using multithreading has me thinking that no only should I make a new class to handle the LocationStream (extending AbstractAppState), but that I should also make another class to extend a controller? Is that right? And then in my Main class set up an executor and queue up Location transmissions? Would I then make yet another class that interfaces with the Controller class? I’m afraid the flow of this is really confused in my head which leads me to…

…iwgeric’s suggestion seems to confuse me even more. What does “queue them locally” mean? When you say “create an app state in the Android project” is this a jMonkey class extending AbstractAppState? Or is this an app state only in the sense that it’s a state of the Android app?

Maybe it would help if I explained the whole project a bit. I’m working on this project where we have built a series of nodes that can be strapped to the joints of someone’s body and the idea is to make a low cost and portable motion capture system. For instance, say you want to work on your golf swing, you could strap these nodes on your body and then record your golf swing so that you can look at it later. These nodes can measure the rotation of each joint and send out information packets containing roll, pitch, and yaw over wifi using UDP. We have this portable battery operated router we want to connect the user’s phone to so that the phone can read all of the incoming packets from all the nodes and save the information to a file.

I want the Android app to behave something like this:
-Allow the user to connect to the local router to receive the packets.
-Have a user press a Start button that will start capturing the UDP packets as well as GPS updates and save the data to a file.
-Be able to send this file as an email attachment so that the user can look at the info on a computer.
-Navigate to a Graphics screen which will show a model that updates its joints in realtime as the UDP packets arrive (right now I am using the Ninja model that comes in the SDK and I have made custom methods that update the joints correctly based on the expected roll, pitch, yaw data in the packets.

Right now I am using Nifty on top of two AppStates, a blank one that is used for the screens that will allow the user to connect to the local wifi network and start/stop data capture, and a Graphics state that shows the model with some navigation buttons below to go back to the connection or logging screens. The reason that I parsed these in to two AppStates instead of just having multiple Nifty screens is that I wanted to be able to pause the Graphics state so that it isn’t unnecessarily using up the processor when the GUI fills the screen.

After getting all the jMonkey/Nifty stuff working (and I’ve tested it on my phone) I have been battling with the best way to add the wifi and GPS stuff. After having my brain melt trying to relate multithreading and controls to wifi or GPS connectivity, I wonder if it would just be easier to try and do the rest in Android using NBAndroid? As you may have guessed, I’m not the world’s best programmer (I’m a mechanical engineer) and everyone lurking around here is probably rolling their eyes and me struggling with what is probably not too hard or a problem. In my defense though, I really have tried to figure this out on my own and I have been constantly perusing the wiki and the forum before I ask my questions.

If you could clarify a bit more on a smart way to accomplish what I’m trying to do and talk to me like I’m the idiot mechanical engineer that I am, I would be greatly indebted.

P-Worm

Interesting project :slight_smile:

Honestly, 99% of what you are asking is android/java programming rather than jme3 programming.

Only the actual visualisation step is jME3.

I don’t want to seem like I’m passing the buck but you may get better answers to the non-jme questions from an android development site as most people here are not developing on android and even many of those that are are only dabbling in it.

Is this a commercial project?

I’m a grad student right now and using this as part of the research that I’m doing. My advisor thinks we have a product on our hand and is thinking of making a kickstarter campaign for the system so that people can have a motion capture system without buying a $10,000 Vicon system. While we were testing this out, I was using Ogre on a desktop computer, but that’s when I found out that Ogre didn’t have a smooth path to putting it on a mobile device unless it is with iOS. That’s when I found jMonkey and in my opinion it is a great package. If we ever do make a kickstarter campaign on this, I’ll be sure to paste a link here if you would like. I’m sure there are a few here that would appreciate having a motion capture system to assist in making games.

As far as coming to this Android sub forum, I wasn’t expecting to receive much help with the actual programming of the GPS or wifi receiver, but instead to see how people connect the two code bases together. Just to get more clarification on these middleman classes that get created to communicate between Android and jMonkey. For instance, the camera example in the wiki I think is a great example, but without something like a flow chart with class names (and what those classes extend, AppState, AbstractControl, etc), I don’t really know what a project “should” look like.

P-Worm

Have you looked at the existing motion capture stuff that does things like linking multiple kinects together?

Not to get too off topic, but yes, and I think they are really cool. I am currently working on my PhD dissertation and my research involves motion capture systems and self calibration procedures. I get to play with the high-end Vicon systems (and let me tell you, they are awesome and amazing) as well as look at things like the Kinect system that manages to give amazingly good results for the cost. The benefit if this nodal sensor network is that it is portable so it allows you to take it outside and run on batteries. I’m a big college football fan and I invasion how the game could change and become more efficient of things like these motion capture nodes could be embedded into the uniforms of the players. Then after a play, the science nerds could analyze the data from the play and improve the team effort by not only knowing where every player was at any given time, but what body their position is in.

P-Worm

You many already know some of this, so forgive me if I’m reiterating something you already know.

When JME is configured to be deployed on Android, a separate Android project is created in the SDK/Netbeans (it is called mobile and is located in a sub directory of the main project). When the main project is built, a jar file of the main project is created and added as a library to the Android specific project. When MainActivity.java of the Android specific project is started on the Android device, it starts the main app by setting the appropriate settings and starting the main app which is really located in the jar file.

The nice thing about how jme runs is that the main application is accessible to the Android specific project through a variable call “app” in MainActivity (which extends AndroidHarness which starts up and controls the lifecycle of the main project).

When we say “on the android side” or “in the android project”, we are talking about classes that are created in the /mobile/src directory outside of the main project in the android specific project that gets created.

Since the android project has access to the main application, there can be communications between the main project and the android specific project. This is necessary when the application is getting/setting data that is Android specific (like gps data).

One way of implementing this communication is to create an interface in the main project and have a variable in the main application of the interface type. Then create a class in the android specific project that implements that interface. Once that is done, you can set the variable in the main project to the class in the android specific project. You need to do this after onCreate in AndroidHarness has completed since that is where the main application is started and the variable “app” is defined. Once this communication between the 2 projects is created, the main project can call a method in the class “on the Android side” that will return any Android specific data that is needed in the main application.

Another way of doing it is to create a class “on the Android side” that implements AppState (or extends AbstractAppState) and add it to the main project by calling app.getStateManager().attach(myClassInTheAndroidProjectThatImplementsAppState); (doing that from memory but I think it’s right). You also need to do this after onCreate is done like above so that the app variable is set to the main application. When you do it this way, jme will automatically call the update() method every time the rendering update loop is executed. Then, from within the update() method of your class, you can then send the gps data to the main application by calling some method in the main application and pass it the data needed.

4 Likes

iwgeric, this is a very informative and useful rundown that clears up a lot of “how this is supposed to work.” In fact, I think adding a post like this to the wiki would be helpful for future people like me.

P-Worm

how to solve a problem P-Worm?
Could you provide the complete code?

<cite>@P-Worm said:</cite> First of all, let me thank all of you for the wonderful jMonkey SDK and the work that has been done on extending jMonkey to Android. I have found the whole system a joy to use and learn.

I am confused on how connecting a jMonkey application to native Android code should be implemented in an expected manor. From the main Android page (that gets linked to countless times in this forum), I read this:

The concept of this makes complete sense to me. It is essentially saying that jMonkey shouldn’t care about where the data comes from and that you should only connect native Android code to a “middle man” that connects the Android code to the main code. However, I am confused by the implementation of this. It is my understanding that AppStates are for when you want the jMonkey game to jump between different states like a main menu, a paused state, or a game running state. Why is this “middle man” class an AppState?

My particular project involves retrieving live GPS data and saving it while the game is going on while also having a character on the screen update based on the user’s location. In this case, it seems to me that the GPS reading needs to either run in its own thread or be used in a callback fashion such as updating the game screen when a new GPS reading comes in. If this is the case, and the “middle man” class is an AppState, doesn’t that break? From what I understand, only one AppState can be running at a time and in the main thread you attach/detach the AppStates as appropriate. Since I want to be gather GPS data constantly, it seems like making the “middle man” class an AppState defeats the purpose (but I also realize I am probably WAY off base in my way of thinking).

Any help or insight that could be provided would be greatly appreciated.

P-Worm

Anyway, this is what I have right now concerning the GPS:

Main
[java]import com.jme3.app.SimpleApplication;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.system.AppSettings;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.CheckBoxStateChangedEvent;
import de.lessvoid.nifty.controls.Label;
import de.lessvoid.nifty.controls.ListBox;
import de.lessvoid.nifty.controls.TextField;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import de.lessvoid.nifty.tools.SizeValue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Main extends SimpleApplication implements ScreenController {

private Nifty nifty;
private BlankState blankState;
private GraphicsState graphicsState;
private LocationStream locationStream;

// Used to test out label updating in GUI
private int switchScreenCounter;

// Flags for whether the program is logging data
boolean loggerRunning;

public static void main(String[] args) {
    Main app = new Main();
    Logger.getLogger("").setLevel(Level.SEVERE);

    app.setDisplayStatView(false);
    app.setShowSettings(false);
    AppSettings newSettings = new AppSettings(true);
    newSettings.setResolution(480, 854);
    newSettings.setFrameRate(60);
    app.setSettings(newSettings);
    app.start();
}

// Connections for GUI
public void bind(Nifty nifty, Screen screen) {
    System.out.println("bindFromMain( " + screen.getScreenId() + ")");
}

public void onStartScreen() {
    System.out.println("onStartScreen Main");
}

public void onEndScreen() {
    System.out.println("onEndScreen Main");
}

public void quit() {
    nifty.gotoScreen("end");
}

/*
 * GUI methods
 */
// Switch states and GUI screens
public void switchScreen(String screenName) {

    if (screenName.equals("graphics")) {
        System.out.println("Switching state to graphics");
        graphicsState.setEnabled(true);
        stateManager.detach(blankState);
        stateManager.attach(graphicsState);
        nifty.gotoScreen(screenName);
    } else {
        System.out.println("Switching state to blank state");
        graphicsState.setEnabled(false);
        stateManager.detach(graphicsState);
        stateManager.attach(blankState);
        nifty.gotoScreen(screenName);
    }
    
    switchScreenCounter++;
    String message;
    message = "Number of screen switches: " + Integer.toString(switchScreenCounter);
    message += " \nplus here's a bunch of \ntext to make sure that \nthe words rollover to \nnew lines when they need to.";
    if (screenName.equals("connection")) {
        updateLabel("checkConnOutput", message);
    }
}

// Changes a label in the GUI
private void updateLabel(String labelId, String message) {
    Label mLabel = nifty.getCurrentScreen().findNiftyControl(labelId, Label.class);
    mLabel.setText(message);
    // As Nifty currently stands, the width of the displayed text does not
    // dynamically change to accomadate larger strings than what is first
    // rendered.
    TextRenderer mRenderer = mLabel.getElement().getRenderer(TextRenderer.class);
    mLabel.setWidth(new SizeValue(mRenderer.getTextWidth() + "px"));
    // Update the panel
    mLabel.getElement().getParent().layoutElements();
    nifty.getCurrentScreen().layoutLayers();
}

// Add an item to the list box and remove the oldest item if the box is
// currently full
private void addItemToLogger(String newItem) {
    ListBox listBox = nifty.getCurrentScreen().findNiftyControl("loggerListBox", ListBox.class);
    int numItems = listBox.itemCount();
    // The list box can only handle 15 items. If the box is already full,
    // pop out the oldest item and add the newest item
    if (numItems == 15) {
        listBox.removeItemByIndex(0);
    }
    
    listBox.addItem("\t"+newItem);
}

// After the user presses the Check Connections button, run through
// connection tests
public void checkConnections() {
    System.out.println("Pressed check connections");
    updateLabel("checkConnOutput", "Button pushed!");
    updateLabel("internetSsidOutput", "Button pushed!");
    updateLabel("mocapSsidOutput", "Button pushed!");
    updateLabel("localIp", "Button pushed!");
    updateLabel("connectionStatus", "Button pushed!");
}

@NiftyEventSubscriber(id="gCheckBox")
public void gCheckBoxToggled(String id, CheckBoxStateChangedEvent event) {
    if (event.isChecked()) {
        updateLabel("checkConnOutput", "3G checkbox is checked!");
    } else {
        updateLabel("checkConnOutput", "3G checkbox is unchecked!");
    }
    
}
    
@NiftyEventSubscriber(id="wifiCheckBox")
public void wifiCheckBoxToggled(String id, CheckBoxStateChangedEvent event) {
    if (event.isChecked()) {
        updateLabel("checkConnOutput", "Wifi checkbox is checked!");
    } else {
        updateLabel("checkConnOutput", "Wifi checkbox is checked!");
    }
    
}
// Start capturing the mocap data
public void startCapturingData() {
    // Set the timer to 0
    timer.reset();
    
    loggerRunning = !loggerRunning;
    
    // Read in the name for the output file
    TextField filenameTextField = nifty.getCurrentScreen().findNiftyControl("filenameInput", TextField.class);
    String filename = filenameTextField.getText();
    updateLabel("connectedIpLabel", filename);
    updateLabel("gpsData1", "1");
    updateLabel("gpsData2", "2");
    updateLabel("gpsData3", "3");
    updateLabel("gpsData4", "4");
}

// Send an email with the data as an attachment
public void sendEmail() {
    // Read info from the email field
    TextField emailTextField = nifty.getCurrentScreen().findNiftyControl("emailEntry", TextField.class);
    String emailAddress = emailTextField.getText();
    TextField filenameTextField = nifty.getCurrentScreen().findNiftyControl("filenameInput", TextField.class);
    String filename = filenameTextField.getText();
    float timeInSec = timer.getTimeInSeconds();
    addItemToLogger(String.format("%.2f: ", timeInSec) + filename);
    updateLabel("connectedIpLabel", filename);
    updateLabel("gpsData1", "1");
    updateLabel("gpsData2", "2");
    updateLabel("gpsData3", "3");
    updateLabel("gpsData4", "4");
}

// Updates the live time logger
private void updateLoggerTimeDisplay() {
    float timeInSec = timer.getTimeInSeconds();
    String timeOutput;
    timeOutput = String.format("%.2f", timeInSec);
    updateLabel("elapsedTime", timeOutput);
}

@Override
public void simpleInitApp() {
    blankState = new BlankState(this);
    graphicsState = new GraphicsState(this);
    locationStream = new LocationStream(this);
    
    loggerRunning = false;

    // Setup the GUI
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
            inputManager,
            audioRenderer,
            guiViewPort);
    nifty = niftyDisplay.getNifty();
    nifty.fromXml("Interface/settingsScreens.xml", "connection", this);
    nifty.addXml("Interface/graphicsScreen.xml");
    guiViewPort.addProcessor(niftyDisplay);
    inputManager.setCursorVisible(true);
    System.out.println("Initialized Main");
    stateManager.attach(blankState);

}

/*
 * Override the main update loop
 */
@Override
public void simpleUpdate(float tpf) {
    // Only update the display if the timer is running and we are on that
    // particular screen
    if (loggerRunning &amp;&amp; nifty.getCurrentScreen() == nifty.getScreen("logger")) {
        updateLoggerTimeDisplay();
    }
}

}[/java]

LocationStream which is my “middle man” class

[java] import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioRenderer;
import com.jme3.input.InputManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;

public class LocationStream extends AbstractAppState {

public SimpleApplication app;
private Node rootNode = new Node("Root Node");
private AssetManager assetManager;
private InputManager inputManager;
private AudioRenderer audioRenderer;
private ViewPort guiViewPort;
private AppStateManager stateManager;

// Will be continuously updated to the location of the device
Location location;

public LocationStream(SimpleApplication app) {
    this.assetManager = app.getAssetManager();
    this.inputManager = app.getInputManager();
    this.audioRenderer = app.getAudioRenderer();
    this.guiViewPort = app.getGuiViewPort();
    this.stateManager = app.getStateManager();

    location = new Location();
}

public Location getLocation() {
    return location;
}

public void setLocation(float longitude, float latitude, int altitude) {
    location.setLocation(longitude, latitude, altitude);
}

@Override
public void initialize(AppStateManager stateManager, Application app) {
    super.initialize(stateManager, app);
    this.app = (SimpleApplication)app;
    System.out.println("Initialized: app is "+this.app);
}

@Override
public void update(float tpf) {
    super.update(tpf);

    rootNode.updateLogicalState(tpf);
    rootNode.updateGeometricState();
}

}
[/java]