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 :)

Tuesday, March 20, 2012

PAW Runtime & PHP

PAW Runtime has been updated to support PHP.
To install PHP for the runtime version of PAW, download the file PawRuntime_PHP_Setup.zip (link below) and unzip it to /sdcard of your Android device.

After starting PAW Runtime a PHP menu entry should be available.
After selecting the entry the page shown below should appear...

PHP Setup
Make your selections and press the Install button. PHP should be available after a restart of PAW Runtime.

If you wold like to get rid of unnecessary files and the menu entry, you can delete the file /sdcard/paw-runtime/webconf/apps/php_setup.conf and everything inside the  /sdcard/paw-runtime/html/php_setup directory except the conf directory.

Links
PAW Runtime
PHP Setup

Update
If you have problems to download above files from the original source, try to use the alternative RapidShare links below:
http://rapidshare.com/files/1766553424/PawRuntime.apk
http://rapidshare.com/files/3080577482/PawRuntime_PHP_Setup.zip

Thursday, February 16, 2012

DavDrive and USB

DavDrive can be used via USB connection. This might come in handy if no WiFi connection is available.

For this to work ADB the Android Debug Bridge has to be installed on the PC.
This is the most complicated part of the setup.

I will not describe how to setup ADB. You will find instructions by asking Google or by watching below Youtube video.


After ADB is set up, the ADB command adb devices should show your device.
In my case (with my Galaxy Nexus) it looks like this:

List of devices attached
01498B1F0C003012    device

Now start DavDrive and execute the following ADB command on your PC:

adb forward tcp:8888 tcp:8888

This will forward the port 8888 of your PC to port 8888 on your Android device.
If you have another port configured use that one.

Now you can use the following URL to access DavDrive on your Android device:

http://localhost:8888

That's it :)

Note: Everything mentioned above is also valid for PAW.

Thursday, January 26, 2012

Changing the PirateBox Location

This post gives a quick overview what has to be done to move the PirateBox installation to a different directory.

By default the PirateBox HTML files are located in the directory /sdcard/paw/html/app/plugins/piratebox_plugin/html and uploaded files are stored in the folder uploads inside the HTML directory.

I'll demonstrate what to do to move the PirateBox to a different directory (in my example /sdcard/piratebox_ext). You can of course also use a directory on an external SD card.

Copying the HTML Directory
First thing to do ist to create the new folder /sdcard/pireatebox_ext.
Now copy the directory /sdcard/paw/html/app/plugins/piratebox_plugin/html to the new location /sdcard/pireatebox_ext.

As a next step we will edit the PAW server configuration.

Changing the PAW Configuration

The configuration file used for PirateBox is named handlers.xml and it's located inside the /sdcard/paw/html/app/plugins/piratebox_plugin/conf directory.
Edit this file and replace each occurence of /sdcard/paw/html/app/plugins/piratebox_plugin with /sdcard/piraebox_ext.

Changing the Web Pages
Last step to do is to edit the files list.xhtml and pb.xhtml inside the /sdcard/piratebox_ext/html directory.
In each file there is one line that defines the upload directory.
Again, change each occurence of /sdcard/paw/html/app/plugins/piratebox_plugin to /sdcard/piraebox_ext.


That's it ... on next startup the new PirateBox location should be used.

Thursday, December 22, 2011

NFC - NDEF Support

NDEF messages are the standard way to write information to NFC tags on Android.
The latest version of PAW (0.84) has support for text, URI, smart poster and mime media NDEF messages.
The classes and their methods are described in the PAW Functions documentation within the PAW web application.
NFC support is now also available in PAW Runtime.
To show how the new functionality can be used (and to give you the possibility to play with NFC tags), a NFC reader/writer plugin is available for PAW. This plugin is also available in the newest add-on package for PAW Runtime.

Here are some screenshots...

Plugin

NFC Plugin
Plugin requesting NFC Tag

Runtime

PAW Runtime with NFC Reader/Writer
Runtime version


This will be the last update of PAW this year and possibly the last blog entry.
To all of you ... Merry Christmas and a Happy New Year :)

Thursday, December 15, 2011

ICS: Getting NFC Tags to Work

Since December I'm a happy owner of a Galaxy Nexus :)
NFC seemed like an interesting thing, so I started to integrate NFC support into PAW.

First thing I did was to order some Mifare Classic 1K tags from tagsfordroid.com.
They arrived fairly quickly and delivery was free of charge.
NFC Tags
To try things out I installed a lot of NFC reader and writer apps. To my dismay none of them seemed to work.
They could read the tags but could not write them. Having no idea how NFC worked, I was fairly disappointed :(

Searching the web I found out that this seems to be a bug in ICS:
http://code.google.com/p/android/issues/detail?id=22258

Googles sais that there will be a fix provided, but when, no one can tell.
So I tried to get it working myself...

If you would like to test the things below, install the latest PAW version (0.80) from the Android Market.

Disclaimer:
This solution is only working for Mifare Classic 1K tags!
If you try as described you do it at your own risk!
It's not my fault if your tags do not work afterwards!


Read the updates at the end of the post first!
This solution is no longer necessary and will most likely damage your tag(s), so do not use it!

The Problem
The problem (as reported in the bug report) seems to be that the tech reported by the tag do not contain
android.nfc.tech.Ndef which is necessary to write NDEF messages.

You can try this with the following code that you can insert into the BenShell Console of the PAW web application:

import de.fun2code.android.pawserver.AndroidInterface;
import android.nfc.NfcAdapter;

intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
if(intent != null) {
  tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  print(tag);
}
else {
  print("No Tag found!");
}

If the result is TAG: Tech [android.nfc.tech.MifareClassic, android.nfc.tech.NfcA] writing NDEF messages will not work!


The Possible Fix
As mentioned above I have no expirience with NFC, so I tried to find a solution that worked without having to know NFC or the Mifare tags in detail.

I searched the web and found some Mifare Classic 1K dumps at the nfc-tools site.
After downloading a dump I tried to write it to the tag. Interesting enough it seemd to work :)

After writing the dump, the above script reported:
TAG: Tech [android.nfc.tech.MifareClassic, android.nfc.tech.NfcA, android.nfc.tech.Ndef]

So here are the step by step instructions:
  1. Download the dump file from the nfc-tools site: mfc_1k__url_nfc-tools.mfd
  2. Save the dump file to the /sdcard of your android device.
  3. Start PAW and open the BeanShell console.
  4. Copy/paste the following script into the console:
    /*
    Mifare Classic - Write dump
    */
    import de.fun2code.android.pawserver.AndroidInterface;
    import android.nfc.NfcAdapter;
    import android.nfc.NdefRecord;
    import android.nfc.NdefMessage;
    import android.nfc.tech.*;
    import android.os.Bundle;
    
    
    intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
    print(intent);
    if(intent != null) {
      tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
      print(tag);
      
      if(tag != null) {
          nfc = MifareClassic.get(tag);
          nfc.connect();
        print("Is connected: " + nfc.isConnected());
    
      
         fis = new FileInputStream("/sdcard/mfc_1k__url_nfc-tools.mfd");
         
         b = new byte[16];
    
         errors = 0;
        
        sectors = nfc.getSectorCount();
        blocksPerSector = nfc.getBlockCountInSector(0);
        
        for(sector=0; sector<sectors; sector++) {
          print("Sector " + sector);
          
          for(block=0; block < blocksPerSector; block++) {
            logicBlock = nfc.sectorToBlock(sector) + block;
            $$.print("  Block: " + block + " ... ");
    
            fis.read(b);
    
            if(sector == 0 && block == 0) {
               print("Manufacturer block ... skipped");
               continue;
            }
    
            try {
              nfc.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT);
              nfc.writeBlock(logicBlock, b);
              print("ok");
            }
            catch(e) {
                print("error");
                errors++;
            }
          }
        }
        
        fis.close();
        nfc.close();
          print("--------------------------------------------------------------------------");
          print("Errors: " + errors);
          print("Dump written!");
       }
       else {
          print("Tag not supported!");
       }
    
    }
    else {
      print("No Tag found!");
    }
    
    
  5. Start the script and as soon as the PAW app is in foreground, place the tag at the back of the phone and leaf it there until the script is finished.
After that the tag is hopefully working.

Good luck and happy hacking :)

Update:
Ndef.getMaxSize() returns only 92 bytes that can be written to a NDEF message. I don't think that's normal :(

You can check this yourself with the following code:
import de.fun2code.android.pawserver.AndroidInterface;
import android.nfc.NfcAdapter;
import android.nfc.tech.Ndef;
 
intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
if(intent != null) {
  tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  print("Max NDEF message size: " + Ndef.get(tag).getMaxSize() + " bytes");
}
else {
  print("No Tag found!");
} 

Update 2:
The latest NFC TagWriter by NXP is able to format unformatted tags and fixes the problem.
Here is the Android Market link: NFC TagWriter by NXP

Thursday, December 1, 2011

PirateBox on Android

A while ago psundegroud created the thread [TUT]Roll Your Own PirateBox! PirateBoxMobile. At that time my only rooted device was an old Samsung Galaxy I7500 running Android 1.6. We managed to get this working. The two main issues were that WiFi Tether was needed, which didn't run on all Android devices and that only Ad-Hoc mode was available. The missing Infrastructure mode prevented most Androids from seeing the PirateBox hotspot.

Note: This post is somewhat outdated. The current state of the PirateBox can be found here: PirateBox Reloaded

Meanwhile I have a rooted Notion Ink Adam running AdamComb v0.3. So I tried to get PirateBox running by using the built in tether app. Built in tethering uses Infrastructure mode, which solves above mentioned problem.

On my Adam this is really working fine and I hope that it will also run on other Android units.
I'll divide the post in two parts. The first part describes the technical details, so if someone has problems in getting this to work this might be helpful for further testing.

Along with setting up the scripts I have also written a plugin for PAW which should make setup really easy.
If you are not interested in the technical details you can right jump to the Installing the Plugin section.

Techical Details

Setting up the PirateBox during boot is done by PAW startup scripts.

The main tasks of these scripts are:
  • Killing dnsmasq and restarting it with changed parameters, so that DNS requests always respond with the IP of the Android device.
  • Configuring iptables so that requests to port 80 are redirected to the port PAW is listening on, because PAW can not operate on privileged ports.
  • Change the PAW configuration to use the PirateBox Handler, which ensures that all unknown request are forwarded to the base URL (piratebox.org/).

The startup scripts are located in the subfolders 0 and 1 of the directory /sdcard/paw/etc/init. These folders represent PAW runlevels. Runlevel 0 is before the server starts and after server shutdown and 1 is after server start and before the server shuts down. Scripts beginning with S_ will be called on startup and scripts starting with K_ will be run at shutdown.

So here is a short summary what these scripts are doing.

0/S_PirateBox.bsh:
  • Checks if tethering is running. If not none of the steps below will be performed.
  • Kills the "original" dnsmasq process and starts a new one.
  • Executes iptables commands to get port redirection working.
  • Replaces the original PAW configuration with the PirateBox configuration.

1/S_SpirateBox.bsh:
  • Checks if tethering is running. If not none of the steps below will be performed.
  • Restores the original PAW configuration.
  • Displays a PirateBox notification.
  • Sets the max upload limit to 200MB.

1/K_S_SpirateBox.bsh:
  • Clears the PirateBox notification.
  • Kills dnsmasq process.
  • Stops tetherig and restarts WiFi.

0/K_S_SpirateBox.bsh:
  • Removes iptables rules for port redirection.

If you run into problems have a look at these scripts and try to run the commands inside the scripts individually.


Installing the Plugin

PAW Plug-in Menu
Installing the plugin is easy. I've put it on the PAW plugin page, so you'll find it within the PAW web application or you can direct download it from the PAW Plugin Page.
After download extract the ZIP file to the /sdcard/paw/html/app/plugins directory of your Android device.
Now after re-entering the PAW web application, the PirateBox plugin should be visible in the Plugins menu.

If the plugin setup screen shows a warning in red, the installation is likely to fail, because some prerequisites are missing.
If no warnings are displayed, press the Install button. You can also try to setup a PirateBox Access Point (AP) automatically. I'm not sure, if this is working on all devices. If it's not working, please create an AP manually.
To uninstall the plugin, press the Uninstall button.

PirateBox Plugin

It is important to note, that PirateBox will only be started if tethering is active before PAW starts up.
Otherwise the normal PAW configuration will be applied. So you can choose between these two configurations.

I hope that's not just working on my Adam but on many other devices as well.

Screenshost
Here are some screenshots from my setup...

PirateBox Startup

Connected to PirateBox

PirateBox on Nexus One
PirateBox in Chromium

Video

Here is a video that shows the PirateBox in action...



Update

Since version 0.3 of the plugin, PirateBox is also working on rooted Galaxy Nexus devices running Ice Cream Sandwich.
Here is a video...