Monday, June 25, 2012

Tinkering with PAW

This blog post is about using Tinkerforge modules together with PAW Server for Android.
As an example a volume control for Android is build.



The Setup
The following image shows the components needed to build the volume control.

Component Overview

The LCD and Poti Bricklets are connected to the Master Brick which is connected via USB to a PC running the brickd (Brick Daemon).
The Poti is used to control the volume of the phone. The values will be displayed on the LCD.

Below is an image of the  setup in action.

Complete Setup


Tinkeforge Classes
Tinkerforge offers a set of language bindings which also includes a Java JAR package.
Because PAW needs DEXed classes the Tinkerforge JAR file has to be converted into DEX format.

To make live easy, here is the link: Tinkerforge_dex.jar

To make the file usable for PAW, copy it int the /sdcard/paw/webconf/dex folder and restart the server.

Inside the Beanshell Console of the PAW web interface you can run the following code to check if the file has been loaded.

print(server.props.get("serviceContext").getDexClassLoader());

For me, the output looks like this:
dalvik.system.DexClassLoader[/mnt/sdcard/paw/webconf/dex/webapp_dex.jar:/mnt/sdcard/paw/webconf/dex/Tinkerforge_dex.jar]

If the output includes the Tinkerforge_dex.jar file, everything should be ok.


Bricklet UIDs
In order to talk to the right Bricklets (LCD and Poti) the UID of these components is needed.
Inside the scripts provided in this post, you have to exchange the connection IP address and Bricklet UIDs accroding to your setup.

To find out the UIDs we can ask the Master Brick obout its configuration.

useDexClasses();

import com.tinkerforge.IPConnection;

// Change IP address!
host = "10.0.0.13";
port = 4223;

ipcon = new IPConnection(host, port); // Can throw IOException

ipcon.enumerate(new IPConnection.EnumerateListener() {
    public void enumerate(String uid, String name, short stackID, boolean isNew) {
        if(isNew) {
            $$.print("Name: " + name);
            $$.print(" | UID: " + uid);
            print(" | Stack ID:" + stackID);
        }
    }
});

Thread.sleep(1000);
ipcon.destroy();

With my setup the output looks like this:
Name: Master Brick 1.0 | UID: apaYNJF7xmN | Stack ID:1
Name: Rotary Poti Bricklet 1.0 | UID: 9BY | Stack ID:2
Name: LCD 20x4 Bricklet 1.0 | UID: 9dj | Stack ID:3


Volume Control Script
One note  about the script...
The script uses polling which is not ideal, actually listeners should be used.
Polling is used for the sake of simplicity.

After we have connected the components and found out the UIDs we can run the volume control script.
Remember to change the IP number and the UIDs.

useDexClasses();

import android.media.AudioManager;
import android.content.Context;
import de.fun2code.android.pawserver.PawServerService;

import com.tinkerforge.IPConnection;
import com.tinkerforge.BrickletRotaryPoti;
import com.tinkerforge.BrickletLCD20x4;

r = new Runnable() { public void run() {

    MAX_LABEL = "Max Volume: ";
    DEGREE_LABEL = "Degree: ";
    VOLUME_LABEL = "Volume: ";

    service = server.props.get("serviceContext");
    audioMgr = service.getSystemService(Context.AUDIO_SERVICE);
    maxVol = audioMgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);


    /*
      UID and IP settings ... these have to be changed!
    */
    host = "10.0.0.13";
    port = 4223;
    UID_LCD = "9dj";
    UID_ROP = "9BY";

    ipcon = null;

    lastVol = -1;
    lcdFirst = true;

    while(true) {
        try {
            // Make sure we have a valid connection
            if(ipcon == null) {
                ipcon = new IPConnection(host, port);
                rop = new BrickletRotaryPoti(UID_ROP);
                ipcon.addDevice(rop);

                lcd = new BrickletLCD20x4(UID_LCD);
                ipcon.addDevice(lcd);
        
                lcd.backlightOn();
            }

            if(lcd.isButtonPressed((short) 0)) {
                break;
            }

            deg = rop.getPosition();
            pos = deg + 150;
            vol = pos * maxVol / 300;
            
            if(lastDeg != deg) {
                if(lcdFirst) {
                    lcd.clearDisplay();
                    lcd.writeLine((short) 0, (short)0, MAX_LABEL + maxVol);
                    lcd.writeLine((short) 1, (short)0, DEGREE_LABEL + deg);
                    lcd.writeLine((short) 2, (short)0, VOLUME_LABEL + vol);
                    lcdFirst = false;
                }
                else {
                    lcd.writeLine((short) 0, (short) MAX_LABEL.length(), "" + maxVol + "  ");
                    lcd.writeLine((short) 1, (short) DEGREE_LABEL.length(), "" + deg + "  ");
                    lcd.writeLine((short) 2, (short) VOLUME_LABEL.length(), "" + vol + "  ");
                }
            }

            if(lastVol != vol) {
                audioMgr.setStreamVolume(AudioManager.STREAM_MUSIC, vol, 0);
            }

            lastDeg = deg;
            lastVol = vol;
        }
        catch(e) { }

        // Wait 100 millis until we poll again
        Thread.sleep(100);
    }

    lcd.clearDisplay();
    lcd.writeLine((short) 0, (short) 0, "Bye bye ...");
    Thread.sleep(1000);
    lcd.clearDisplay();
    lcd.backlightOff();

    ipcon.destroy();
    ​
}};

t = new Thread(r);
t.start();

Some notes about the script:
  • If button 0 of the LCD is pressed, the script will terminate.
  • The Android AudioManager is used to control the music stream volume.
The below picture shows how the display looks like on the LCD.

LCD Output


Android restarts the PAW service from time to time (for whatever reason). So if the scripts stops working just restart it again.
You can also put the script (name is something  like S_tf_volume.bsh) inside the /sdcard/paw/etc/init/1 folder to make sure it's run each time the servers starts up.

Hope this was interesting ... have fun :)