Posts tagged: JavaFX

Our Live-Ticker implementation – World cup 2014

Post to Twitter

We got a lot of (positive) feedback for our live ticker in Packung! It’s great to see actual results depending on the current score :)

We started our live ticker in 2008 with a simple implementation in Java. The solution simply sniffed results from standard html pages. It was easy to get results because result pages didn’t use Ajax. It was harder in 2010 and 2012 because Ajax was heavily used. But we found some pages without Ajax.

What’s the problem with Ajax? It’s not possible to get the complete html content with one simple http request with Java. You need a “browser” that handles javascript and async calls.

We didn’t find a html page with live results this year, which didn’t use Ajax or Flash. So we thought about a clever solution because we won’t pay for “free content”. And of course, our betting game is free and we don’t earn money. But what’s a clever solution? The task is simple: Grab results from existing live-ticker website (be aware of Ajax) – but how?

The solution is simple: Use a browser :)

But Java doesn’t offer a browser control, doesn’t it? Yes, JavaFX does!

In 2008 we played around with QT webkit control of QT Jambi (but the project is not active) because it was a nice browser control. The problem was that it didn’t work headless and our application server was a linux box without X-Server. This year, we had the same restrictions because our server is a linux box and still no X-Server. We thought that JavaFX could be the right solution for us…

BUT it doesn’t work headless enough – not with latest release. It will be better in upcoming releases and/or custom JavaFX builds because there is a Glass windowing component called Monocle. But this was the right challenge for us.

Our current solution runs on Linux, without a real X-Server. It’s a JavaFX WebView/WebEngine that reads data from a public live-ticker.

How our solution works?

Install important linux packages:

yum install xorg-x11-server-Xvfb xorg-x11-xauth gcc glib2 glib2-devel libtiff libtiff-devel libjpeg-devel cairo cairo-devel pango pango-devel redhat-lsb

optional if available

yum install redhat-lsb-graphics

Download and install ATK

wget ftp://ftp.muug.mb.ca/mirror/centos/6/os/x86_64/Packages/atk-1.30.0-1.el6.x86_64.rpm
wget ftp://ftp.muug.mb.ca/mirror/centos/6/os/x86_64/Packages/atk-devel-1.30.0-1.el6.x86_64.rpm
rpm -Uvh *.rpm

and GTK+

wget http://ftp.gnome.org/pub/gnome/sources/gtk+/2.18/gtk+-2.18.2.tar.gz
tar -xvf gtk+-2.18.2.tar.gz
cd gtk+-2.18.2
./configure
make
make install

Start Xvfb:

Xvfb :99 &

Start application:

#!/bin/sh
export DISPLAY=:99
export LD_LIBRARY_PATH=/usr/local/lib
./jdk1.8.0_05/bin/java -jar livescore.jar >> liveupdate.log 2>> liveupdate_err.log &

The application itself is trivial. It’s a class with main method:

/**
 * Starts the application.
 *
 * @param pArgs the application parameters
 */

public static void main(String[] pArgs)
{
    new Livescore();
}

/**
 * Creates a new instance of <code>Livescore</code> and starts the update procedure.
 */

public Livescore()
{
    super();

    fxPanel = new JFXPanel();
       
    Platform.runLater(new Runnable()
    {
        public void run()
        {
            fxPanel.setScene(new Scene(new BorderPane(), 800, 600));
               
            browser = new SimpleBrowser();
            browser.load();
        }
    });
       
    start();
}

We need the JFXPanel because JavaFX needs a scene, otherwise it’ll throw Exceptions. We don’t need the instance for other things.

The SimpleBrowser class is similar to this snippet:

/**
 * Loads the configured website.
 */

public void load()
{
    if (view == null)
    {
        view = new WebView();
        engine = view.getEngine();

        engine.load("http://live-ticker.url");
    }
    else
    {
        engine.reload();
    }

    if (thCheck == null)
    {
        thCheck = new Thread(new Runnable()
        {
            public void run()
            {
                try
                {
                    while (Thread.currentThread() == thCheck)
                    {
                        Platform.runLater(new Runnable()
                        {
                            public void run()
                            {
                                checkResults();
                            }
                        });
                       
                        Thread.sleep(10000);
                    }
                }
                catch (InterruptedException ie)
                {
                    //stop
                }
               
                thCheck = null;
            }
        });
        thCheck.start();
    }
}

That’s it. We got two “silent” Exceptions

java.lang.NullPointerException
  at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:57)
  at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
  at java.lang.reflect.Field.get(Field.java:387)
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.getFieldValue(ScrollBarThemeImpl.java:397)
 …
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.thumbPosition(ScrollBarThemeImpl.java:267)
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.getThumbPosition(ScrollBarThemeImpl.java:352)
  at com.sun.webkit.Timer.twkFireTimerEvent(Native Method)
  at com.sun.webkit.Timer.fireTimerEvent(Timer.java:66)
  at com.sun.webkit.Timer.notifyTick(Timer.java:47)
  at javafx.scene.web.WebEngine$PulseTimer$2.pulse(WebEngine.java:1154)
  …

after the application start, because of some scrolling problems, but these Exceptions had no effect (in our solution).

The solution would rock with a real headless JavaFX but it’s ok for us.

JVx with JavaFX and Vaadin and Exchange appointments

Post to Twitter

Wow, what a title :)

Some weeks ago, I blogged about JVx with Exchange servers. The project now supports Appointments and Tasks.

To demonstrate some features, we created an application that integrates powerful frameworks. We took Vaadin with Calendar AddOn, JavaFX’ webview and integrated all together in a standard JVx application.

Take a look:

JVx + JavaFX + Vaadin + Exchange

JVx + JavaFX + Vaadin + Exchange



Did you notice that the calendar is a JavaFX WebView?

Our exchange storages still use the EWS Java API but now with some tweaks.
More information will follow in other posts…

Control your window shutters with JavaFX

Post to Twitter

If you have remote controlled, electronical, window shutters, continue reading. If you don’t have them, this article might not be interesting for you?
I have such window shutters and a lot of windows. I don’t own a control center that allows me to automatically open and close my shutters every morning and evening. I do it every day manually. Sometimes sooner and sometimes later – depends on the day of the week.

Why I don’t own a control center?
Such control centers are expensive and do not offer expected features. Some of my features are controlling via SMS, controlling via Internet, move shutters to a specific position (not only up and down).

I didn’t really search for a solution of my “problem” because it was not a problem, it was an everyday task and it was habitual. Since I own a RaspberryPi, I tried to find useful solutions, not only Hello worlds. That’s the reason why I tried to control my window shutters via Raspberry Pi.

The first big problem was, that it was not possible to find a sender for my shutters. The frequency was 433,42 MHz, but there were no ready-to-use solutions. It was not my intention to solder my own sender and record and decrypt the signal. So, I decided for the easy way: Connect an existing remote control with my Pi. It makes no difference for the remote control, if you press buttons manually or a Pi does the job.

It was not a big problem to find some cheap 3V relais. The soldering was fun and not rocket science. Here is the result:

Remote control

Remote control

My remote control got a new brain. It works with and without connected Pi. I didn’t damage it :)
The only thing I had to do, was programming every receiver with one (or more) channel of my remote control. Because one button should control all window shutters. But that was a task of 5 minutes.

The hardware was ready and the next step was a fancy client application for controlling my Pi. The plan was a simple application that shows one of my windows. The shutter should move like the original. The application should connect to my Pi via my LAN.
I used JVx and vert.x on server-side for the GPIO control, wrote a nice Deskotp application with JavaFX, some transitions and JVx for the communication to my server. And here is the result:


JavaFX application

The application moves the shutter like its big brother. One feature is transparency on mouse over, because you wanna know the position if you stop it.

And if you are interested how software and hardware acts together:


JavaFX Shutter control

The project was really cool because the source code for the whole thing was minimal, the costs were not comparable to a control center from the manufacturer and I have all features I ever wanted.

Interested in the source code?

Server-side, JVx style:

//—————————————————-
// Application.java
//—————————————————-

public GpioController getController()
{
    GpioController gpio = (GpioController)get("controller");
       
    if (gpio == null)
    {
        gpio = GpioFactory.getInstance();
               
        put("controller", gpio);
    }
       
    return gpio;
}

public GpioPinDigitalOutput getDownPin()
{
    GpioPinDigitalOutput pin = (GpioPinDigitalOutput)get("downPin");
       
    if (pin == null)
    {
        pin = getController().provisionDigitalOutputPin(RaspiPin.GPIO_01, "Down",
                                                        PinState.LOW);
               
        put("downPin", pin);
    }
       
    return pin;
}

//———————————————
// Session.java
//———————————————

public void moveDown() throws Exception
{
    GpioPinDigitalOutput pin = getDownPin();
       
    pin.high();
    Thread.sleep(2000);
    pin.low();
}

And my client simply calls:

NetSocketConnection con = new NetSocketConnection("10.0.0.39");
                       
macon = new MasterConnection(con);
macon.setApplicationName("shutter");
macon.setUserName("user");
macon.setPassword("pwd");
macon.setAliveInterval(-1);
macon.open();

macon.callAction("moveDown");

RaspberryPi JavaFX + Sound

Post to Twitter

Media support is not included in the current JDK8 preview. If you want to play audio files, it’s maybe a problem if you think that it’s not possible :)

Don’t use JavaFX for the job. Simply use Java Sound API. We successfully used it for our tests with Beagleboard-xm and RaspberryPi (jdk7, jdk8 javafx preview).

Here’s the source code of our, old but still good, implementation:

/*
 * Copyright 2012 SIB Visions GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 *
 * History
 *
 * 15.12.2005 – [JR] – creation
 */

package com.sibvisions.apps.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Vector;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;

import com.sibvisions.util.type.FileUtil;

/**
 * The <code>AudioPlayer</code> plays audio files. Currently only wav, au and midi files are supported.
 *
 * @author René Jahn
 */

public final class AudioPlayer
{
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Class members
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  /** thread list for background playback. */
  private static Vector<Thread> vThreads = null;
 
  /** list of dataline connections for playback. */
  private static Vector<SourceDataLine> vSdl = new Vector<SourceDataLine>();
 
  /** Cached audio files. Reduces resource usage during repatedly playback. */
  private static Hashtable<String, byte[]> htAudioCache = new Hashtable<String, byte[]>();
 
  /** sync object. */
  private static final Object SYNC = new Object();
 
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Initialization
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  /**
   * Invisible constructor because <code>AudioPlayer</code> is a utility
   * class.
   */

  private AudioPlayer()
  {
  }
 
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // User-defined methods
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  /**
   * Gets the current player thread count.
   *
   * @return the number of player threads
   */

  public static int getPlayerThreads()
  {
    if (vThreads != null)
    {
      return vThreads.size();
    }
    else
    {
      return 0;
    }
  }
 
  /**
   * Playback an audio file.
   *
   * @param pFileName the filename
   * @param pBackground <code>true</code> to start playback as background process. The method call returns
   *                    immediately.
   */

  public static synchronized void play(final String pFileName, boolean pBackground)
  {
    AudioPlayer.play(new String[] {pFileName}, 0, pBackground);
  }

  /**
   * Playback multiple audio files.
   *
   * @param pFileName list of file names
   * @param pDelay delay between playback
   * @param pBackground <code>true</code> to start playback as background process. The method call returns
   *                    immediately.
   */

  public static synchronized void play(final String[] pFileName, final long pDelay, boolean pBackground)
  {
    Thread thPlay;
    Thread th;

   
    if (pBackground)
    {
      thPlay = new Thread()
      {
        public void run()
        {
          if (Thread.currentThread() == this)
          {
            if (pFileName != null)
            {
              for (int i = 0, anz = pFileName.length; i < anz; i++)
              {
                AudioPlayer.play(pFileName[i]);
               
                //Use defined delay between playback, but ignore delay after the last track
                if (pDelay > 0 && i + 1 < anz)
                {
                  try
                  {
                    Thread.sleep(pDelay);
                  }
                  catch (InterruptedException ie)
                  {
                    //nothing to be done
                  }
                }
              }
            }
          }
         
          vThreads.remove(this);
        }
      };
     
      if (vThreads == null)
      {
        vThreads = new Vector<Thread>();
      }
     
      //Clear zombies
      for (int i = 0; i < vThreads.size();)
      {
        th = (Thread)vThreads.get(i);
       
        if (!th.isAlive())
        {
          th.interrupt();
          vThreads.remove(i);
         
          th = null;
        }
        else
        {
          i++;
        }
      }
     
      vThreads.add(thPlay);
     
      thPlay.setPriority(Thread.MAX_PRIORITY);
      thPlay.start();
    }
    else
    {
      if (pFileName != null)
      {
        for (int i = 0, anz = pFileName.length; i < anz; i++)
        {
          AudioPlayer.play(pFileName[i]);
         
          //Use defined delay between playback, but ignore delay after the last track
          if (pDelay > 0 && i + 1 < anz)
          {
            try
            {
              Thread.sleep(pDelay);
            }
            catch (InterruptedException ie)
            {
              //nothing to be done
            }
          }
        }
      }
    }
  }
 
    /**
     * Playback a sound file.
     *
     * @param pFileName the filename
     */

    public static void play(String pFileName)
    {
    AudioInputStream ais = null;
 
    AudioFormat adf;
   
    DataLine.Info dli;
   
    SourceDataLine sdl;
   
    byte[] byData = new byte[128000];

    int iData;
   
   
    try
    {
      byte[] byCache = (byte[])htAudioCache.get(pFileName);
     
      if (byCache != null)
      {
        ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(byCache));
      }
      else
      {
        File fiDirect = new File(pFileName);
       
        //use byte array because most InputStreams do not support "seek"
        //we avoid Exceptions
        if (fiDirect.exists())
        {
          byCache = FileUtil.getContent(new FileInputStream(fiDirect));
        }
        else
        {
          byCache = FileUtil.getContent(AudioPlayer.class.getResourceAsStream(pFileName));
        }

        if (byCache != null)
        {
          cacheFile(pFileName, byCache);

          ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(byCache));
        }
        else
        {
          throw new IllegalArgumentException("No content found for : " + pFileName);
        }
      }
     
      byCache = null;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return;
    }

    adf = ais.getFormat();
    dli = new DataLine.Info(SourceDataLine.class, adf);

    try
    {
      sdl = (SourceDataLine) AudioSystem.getLine(dli);
      sdl.open(adf);
     
      synchronized (SYNC)
      {
        vSdl.add(sdl);
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return;
    }

    sdl.start();

    try
    {
      while ((iData = ais.read(byData, 0, byData.length)) != -1)
      {
        sdl.write(byData, 0, iData);
      }
    }
    catch (IOException ioe)
    {
      ioe.printStackTrace();
    }

    sdl.drain();

    synchronized (SYNC)
    {
      sdl.flush();
      sdl.stop();
     
      sdl.close();
     
      vSdl.remove(sdl);
    }

    try
    {
      ais.close();
      ais = null;
    }
    catch (IOException ioe)
    {
      //nothing to be done
    }
   
    sdl = null;
    adf = null;
    }
 
    /**
     * Stop playback of all audio files.
     */

    public static void stopSound()
    {
      synchronized (SYNC)
      {
        if (vSdl != null)
        {
        //stop all playbacks
        for (SourceDataLine sdl : vSdl)
        {
          try
          {
            sdl.flush();
            sdl.stop();
         
            //Don’t call this because it crashes the VM and results in a core dump
            //sdl.close();
          }
          catch (Exception e)
          {
            //nothing to be done
          }
        }
       
        vSdl.clear();
        }
      }
    }
   
    /**
     * Caches audio data for later use. If the file is already cached the cache won’t
     * be overwritten.
     *
     * @param pFileName the filename
     * @param pData audio data
     */

    public static void cacheFile(String pFileName, byte[] pData)
    {
      cacheFile(pFileName, pData, false);
    }

    /**
     * Caches audio data for later use.
     *
     * @param pFileName the filename
     * @param pData audio data
     * @param bIgnoreCache <code>true</code> to overwrite already cached data, <code>false</code>
     *                     doesn’t overwrite cached data
     */

    public static void cacheFile(String pFileName, byte[] pData, boolean bIgnoreCache)
    {
      if (!bIgnoreCache && htAudioCache.containsKey(pFileName))
      {
        return;
      }
     
      if (pFileName != null && pData != null)
      {
        htAudioCache.put(pFileName, pData);
      }
    }
   
    /**
     * Caches an audio file for later use. The file content will be read automatically.
     * If the file is already cached the cache won’t be overwritten.
     *
     * @param pFileName the filename
     */

    public static void cacheFile(String pFileName)
    {
      cacheFile(pFileName, false);
    }

    /**
     * Caches an audio file for later use. The file content will be read automatically.
     *
     * @param sFileName Dateiname
     * @param bIgnoreCache true um bereits gecachte Daten zu überschreiben
     */

    public static void cacheFile(String sFileName, boolean bIgnoreCache)
    {
      //don’t overwrite cache, if not specified
      if (!bIgnoreCache && htAudioCache.containsKey(sFileName))
      {
        return;
      }
     
      InputStream is = AudioPlayer.class.getResourceAsStream(sFileName);
     
      ByteArrayOutputStream bos = new ByteArrayOutputStream();

    byte[] byData = new byte[128000];
     
      int iData;
     
     
      try
      {
      while ((iData = is.read(byData, 0, byData.length)) != -1)
      {
        bos.write(byData, 0, iData);
      }
   
      htAudioCache.put(sFileName, bos.toByteArray());
     
      bos.close();
      is.close();
      }
      catch (IOException ioe)
      {
        //nothing to be done
      }
    }
   
} //AudioPlayer

Example wav file.

Power control with RaspberryPi, JVx and Vert.x

Post to Twitter

My idea was to create a programmable timer for some lamps at home. I wanted to control the timer via mobile phone (not only smartphone). I decided to use simple SMS’.

The timer app was not really a challenge and the SMS interface was also very straight forward, so I added a little bit network communication and complexity. The result:

Power control

Power control

My Hardware:

  • Raspberry Pi Model B
  • Cinterion TC65i
  • Standard table lamp
  • Solid State Relay (found here)
  • Laptop as cluster client
  • Mobile Phone :)

My software:

My first question was – Which application server should I use for my Pi? I thought that JBoss or Tomcat would be overhead and I would save resources. I decided for Vert.x and a simple socket server. I wanted to show the integration of Vert.x with JVx and so my server got a JVx application with some business logic for publishing messages.

The server “thing” was clear and for reading SMS’ I wrote a simple polling app. The app reads SMS from my hardware via RS232 and forwards received SMS via JVx APIs to my server (with Vert.x’ NetClient).
I didn’t use the EventBus for sending messages becasue I wanted to show the seamless integration of JVx and Vert.x.

The code for forwarding messages:

MasterConnection macon = new MasterConnection(new NetSocketConnection("10.0.0.39"));
macon.setApplicationName("demo");
macon.setUserName("user");
macon.setPassword("password");
macon.setAliveInterval(-1);
macon.open();

macon.callAction("publishMessage", msg.getOriginator(), msg.getText());

The server application is a standard JVx application (only server-side). It uses a standard XmlSecurityManager without the need for a database. The “business logic” is very simple:

public Vertx getVertx()
{
    return (Vertx)get("vertx");
}

public void publishMessage(String pNr, String pMessage)
{
    System.out.println("publishMessage (" + pMessage + ")");
       
    String sNumber = pNr;

    sNumber = sNumber.substring(0, 5);

    //hide last characters
    for (int i = 5; i < pNr.length(); i++)
    {
        sNumber += "*";
    }
       
    JsonObject jsobj = new JsonObject();
    jsobj.putString("number", sNumber);
    jsobj.putString("message", pMessage);
       
    getVertx().eventBus().publish("sms.received", jsobj);
}

I made some tests with vert.x clustering and decided to implement a (very) simple JavaFX application that shows published SMS’. The application contains following code:

vertx = Vertx.newVertx(25502, "10.0.0.11");

EventBus ebus = vertx.eventBus();
ebus.registerHandler("sms.received", new Handler<Message<JsonObject>>()
{
    public void handle(final Message<JsonObject> pMessage)
    {
        Platform.runLater(new Runnable()
        {
            public void run()
            {
                try
                {
                    mdb.insert(false);
                    mdb.setValues(new String[] {"NUMBER", "MESSAGE"},
                                  new String[] {pMessage.body.getString("number"),
                                                pMessage.body.getString("message")});
                    mdb.saveSelectedRow();
                }
                catch (ModelException me)
                {
                    throw new RuntimeException(me);
                }
            }
        });
    }
});

Finally, I created a short video that shows all in action. It shows three applications on the same laptop. The first is the JavaFX application that shows published SMS. The second is the Power control (includes SMS check), started via PuTTY. The third application is the server instance, started via PuTTY. You can see a table lamp that will be turned on and off with SMS, sent from a smartphone.

Enjoy it :)


Power Control with RaspberryPi

Welcome RaspberryPi

Post to Twitter

I ordered a Raspberry Pi model B two month ago from RS-Components… it arrived today. It was not a problem because I had a Beagleboard for tests and JavaFX for Pi is available since yesterday.

One of my friends received a (more expensive) Pi some weeks ago and we made some tests with JBossAS 7.1 and JavaSE 7 (without JavaFX). It worked like a charm and it’s cool to have a JBossAS on a Pi. But without JavaFX it was not too funny.

So, back to my Pi.

The installation was very easy: Simply read the short version of Stephen Chin or the long version.

I had some smaller problems:

  • Win32DiskImager did not work with my Laptop and the builtin card-reader, and I had no external reader
  • I didn’t know the username and password for login

I solved the first problem with flashnul:

flashnul 2 -L 2012-12-16-wheezy-raspbian.img

My first attempt didn’t work – Raspberry Pi didn’t boot (no flashing lights on my Pi – only PWR, screen was black) …. Wrote the image once again and removed the device cleanly from my Laptop – worked.

The second problem was not really a problem, but I had no idea. Used pi as username and raspberry as password (see here)

First actions after boot and setup:

apt-get update
apt-get install librxtx-java mc locate

Pi is up and running, but the image is not comparable with my Angstrom image. Some modules/drivers are missing… but that’s not too important now.

My Time tracking software works on my Pi without problems. Startup time is a little bit longer as on my Beagleboard.

The next steps are some performance tests…

But Pi is definitely a MUST have.
Order your own one asap and enter the world of embedded systems with JavaFX.

Time tracking in Action

Post to Twitter

If you read previous articles about my experiments with JavaFX and embedded hardware you might be interested in the result.

SIB Visions card

SIB Visions card

  This is the NFC card reader and one of our user cards.

Hardware and case

Hardware and case

  This is the whole system with a nice looking case. This case is hand-made and you can’t buy it in a store :)

Beagleboard-xm

Beagleboard-xm

  The back shows the Beagleboard.

Finally, a short video because the solution needs sound to be cool:


Time tracking in action


There are 3 different sounds: OK, OK + completed (whistle) and ERROR. If you hear OK, it means “come in”, OK + completed means “Go out”. An ERROR means “unknown card”.

Have fun.

JavaFX TimeTracking with Beagleboard-xm

Post to Twitter

My first JavaFX “embedded” application is ready.

The application is a time tracking tool. It uses RFID cards for user authentication and sends time entries to a remote Redmine project management tool. It works great…. now.

Before I continue, a screenshot of the current application taken via VNC from my Beagleboard:

Time tracking

Time tracking

… I’m not a UI designer ;-)

My first design was a little bit different because I began to develop on my Windows Laptop and used some nice transitions and effects. The first layout was this:

Time tracking (Laptop)

Time tracking (Laptop)

Some problems with this design – on my Beagleboard – were bad peformance of TableView updates, slow transitions, problems with innershadow effect:

Time tracking (1:1)

Time tracking (1:1)

I don’t know why the shadow effect worked for the table border, but the UI was not usable. It was too slow and the clock was not shown because of other problems described from Gerrit Grunwald.

After short frustration, I began to tweak:

  • Replaced TableView with ListView
  • Replaced Clock from JFXtras with a styled Label
  • Smaller buttons for better performance
  • Specific stylesheets for embedded environment

It was not a lot of work to change the application, but I thought the performance would be better. It’s very important to reduce effects and transitions for applications that should run on an embedded device like Beagleboard-xm. It’s a little bit like developing mobile applications (touch instead of mouse, screen size, performance, …).

The current JavaFX embedded runtime is not perfect and the performance could be better, but if you are careful, you can create very useful applications.

And finally a picture of my hardware:

Hardware

Hardware

JavaFX development for Beagleboard xm

Post to Twitter

TableView

TableView

  This is my first application for the board.
It’s not rocket science but was already developed :)

This screenshot was captured via VNC. The installation of VNC was very easy:

opkg install angstrom-x11vnc-xinit
killall Xorg

Wait until X was restarted and use a VNC Viewer (e.g. TightVNC) to create a connection to port 5900. More details are available here.

The application was developed with JavaFX 2.2.2, but on the beagleboard (with the current preview version) it is version 8.0.0-ea-b55. If you develop custom controls be careful because SkinBase was moved to javafx.scene.control. If you want develop with an 8.x version, you must download a JDK8 EA version. It works for me but some methods are different in 8.x as in 2.x and of course they removed some deprecated “impl_” methods :)

Another problem with my test application is that I have no scrollbars (see screenshot) and scrolling with my mouse does not work. Sort on header and resize columns is not possible.

Don’t expect too much from the current JavaFX support for ARM. It’s in a very early phase, but if you create your own “controls” you get a powerful toy.

JavaFX + ScenicView + Javeleon = LiveFX

Post to Twitter

JavaFx + ScenicView

JavaFx + ScenicView

  If you use JavaFX for development, normally you create applications with a GUI – so far so good. How often do you restart your application to check if a simple code change works? Many times?

If you develop a control, wouldn’t it be great to see your code changes immediately?

We use Javeleon for application development together with JVx because it saves a lot of development time and creates live feeling. We thought that Javeleon could also be useful for JavaFX application or control development because application restarts always waste development time.

The integration of Javeleon in your JavaFX application is not difficult, simply add two VM arguments

-Xbootclasspath/p:"D:\temp\JaveleonBoot\default"
-javaagent:D:\libs\javeleon\javeleon.jar

to your launch/run configuration (use your directories). If you use Javeleon for development, it reduces application restarts to a bare minimum, but you should know the limits.

Javeleon reloads classes directly in your VM, but it does not recreate members of your loaded instances and does not call the constructor of your classes again.

You should use a Javeleon reload listener to trigger reload of resource files (css, xml) and call specific methods after reload e.g. call init again.

Imagine you have a GUI with a button and on click you start the creation of a report or execute some business logic, etc. If you change the source code for your button click action or change a method used in the action, your application always invokes the changed “code”.

Sounds tricky? It isn’t, but live coding is not well known.

If you are interested, simply try it out!

I’m not sure whether the current downloadable Javeleon 3.0 is up-to-date because I found some smaller problems with ScenicView. The problems were already fixed and hopefully released asap.
My working version number: Javeleon version 3.0.1 dev 2012-11-13 (r699).

If you have problems, simply contact the Javeleon team.