Welcome to My Scratchpad Friday, October 11 2024 @ 07:36 am BST

Android widget connected via the web to Arduino

  • Views: 66,393

 

This is my first Android application.

I thought I would combine it with my ongoing weather station Arduino project.
Taking the xml generated by the Arduino project and displaying it on the home screen of my HTC Hero in a simple self updating widget.

 

The Arduino project has temperature sensors inside and outside the house. Displaying the temperatures on an LCD screen with minimum, maximum values and the time from a battery backed real time clock. It also has an ethernet board attached to serve a web page showing these values and the values from the last 24 hours. As well as a graph of the values, generated by the google graph api. Also there is a page responding with XML. At the moment with just the current time, internal and external temperatures
The widget retrieves the XML over the web from my project sitting behind my home firewall.
I use DynaDns to supply a URL linked to my home ip address and forward the port on my firewall to the Arduino ethernet board.

The returned XML looks like this:

<temp>
  <time>16:17 - 20 1 2010</time>
  <int>15.25</int>
  <ext>4.43</ext>
</temp>

Its quite simple due to the memory limitations of the Arduino.


The widget takes the values and displays them within the widgets text field.
There is a click event associated with the text field of the title and causes the widget to get the latest XML from the weather station and updates the display. While the home screen is visible the widget updates and then again every hour. You have to be careful with updates as the battery of the Android device can run low very quickly if you keep waking it up to get the latest XML response.

The Android widget is made up of the following files.

AndroidManifest.xml
src
   - WSWidget.java
res
   - drawable
       - icon.png
       - widget_bg.9.png
   - layout
       - main.xml
   - values
       - strings.xml
   - xml
       - widget.xml

 

AndroidManifest.xml

This contains the necessary information for the home screen to include the widget such as the main class of the application and the permissions to use services or devices on the hardware platform.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.thisandthose.WSWidget"
      android:versionCode="1"
      android:versionName="1.0">
      <uses-permission android:name="android.permission.INTERNET"/>
    <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
    <receiver android:name=".WSWidget" android:label="@string/widget_name">
    <intent-filter>
    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    <action android:name="org.thisandthose.WSWidget.CLICK"></action>
    </intent-filter>
    <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget" />
    </receiver>
    </application>
    <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="4"/>

</manifest>

WSWidget.java

Contains the class WSWidget containing the methods required to create and manage the widget.

/**
 * @author Gordon Endersby
 * Example Android widget to receive xml and display
 * it a formatted manner on the home screen
 *
 */

package org.thisandthose.WSWidget;

import java.net.URL;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.util.Log;

/**
 * The class representing our widget
 *
 */
public class WSWidget extends AppWidgetProvider {

    // Global variables
    private String wStationURLXml = "http://xxxxxxxxxxxx"; // Url for the xml
 
   
    /**
     * Gets the component name representing this widget.
     * Needed in onReceive to get the WidgetIds.
     */
    static final ComponentName THIS_APPWIDGET =
        new ComponentName("org.thisandthose.WSWidget", "org.thisandthose.WSWidget.WSWidget");

    /*
     * Override of android.appwidget.AppWidgetProvider#onReceive(android.content.Context, android.content.Intent)
     * Receives any Intents aimed at the widget as defined in the widgets manifest.
     * In this case listens for the click on the title text field in the widget
     */
    @Override
    public void onReceive(Context context, Intent intent)
    {
        super.onReceive(context, intent);
       
        String text = "";
               
        // Is this our click intent
        if(intent.getAction().equals("org.thisandthose.WSWidget.CLICK"))
        {
            // Get the AppWidgetManager and AppWidgetIds from the context as they are out
            // of scope of this overridden method.
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
            int[] appWidgetIds = appWidgetManager.getAppWidgetIds(THIS_APPWIDGET);

            // Display a warning that the text is updating
            UpdateTextField(context, appWidgetManager, appWidgetIds, "Updating");
            // Get the text to display
            text =  GetText( wStationURLXml);
            // Display the text in the widget
            UpdateTextField(context, appWidgetManager, appWidgetIds, text);
        }
    }

    /*
     * Override of android.appwidget.AppWidgetProvider#onUpdate(android.content.Context, android.appwidget.AppWidgetManager, int[])
     * Specific to this widget. Called by the home screen when it needs all the widgets
     * on the screen to be updated and when the widget is first added to the home screen.
     */
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

        String text = "";
       
        // Adds the onClick action to all instances of the widget
        final int N = appWidgetIds.length;
        for (int i=0; i<N; i++)
        {
            int[] appWidgetId = appWidgetIds;
            RemoteViews views=new RemoteViews(context.getPackageName(), R.layout.main);
            Intent clickintent=new Intent("org.thisandthose.WSWidget.CLICK");
            PendingIntent pendingIntentClick=PendingIntent.getBroadcast(context, 0, clickintent, 0);
            views.setOnClickPendingIntent(R.id.title, pendingIntentClick);
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }

        // Display a warning that the text is updating
        UpdateTextField(context, appWidgetManager, appWidgetIds, "Updating");
        // Get the text to display
        text =  GetText( wStationURLXml);
        // Display the text in the widget
        UpdateTextField(context, appWidgetManager, appWidgetIds, text);

    }

    /**
     * Gets the value from the tag without formatting
     * @param tag    - Name of tag
     * @param doc    - DOM representing the XML
     * @return        - String value
     */
    private static String getTagValue(String tag, Document doc) {
        Element intElement = (Element) doc.getElementsByTagName(tag).item(0);
        return intElement.getChildNodes().item(0).getNodeValue();
    }

    /**
     * Gets the value from the tag with a little padding out of the returned string
     * @param tag    - Name of tag
     * @param doc    - DOM representing the XML
     * @return        - String value
     */
    private static String getTagValueTemp(String tag, Document doc) {
        Element intElement = (Element) doc.getElementsByTagName(tag).item(0);
        String temp = intElement.getChildNodes().item(0).getNodeValue();
        if (temp.length() == 4){ temp = " " + temp;}
        return temp;
    }

    /**
     * Updates the text displayed in the widget
     * @param context            - Context of the widget and its environment on the home screen.
     * @param appWidgetManager    - widget manager.
     * @param appWidgetIds        - ID's of any instance of the widget on the home screens
     * @param text                - Text to display.
     */
    private static void UpdateTextField(Context context,AppWidgetManager appWidgetManager, int[] appWidgetIds, String text){
        // Change the text in the widget
        RemoteViews updateViews = new RemoteViews( context.getPackageName(), R.layout.main);
        updateViews.setTextViewText(R.id.text, text);
        appWidgetManager.updateAppWidget(appWidgetIds, updateViews);
    }

   
    /**
     * Gets the XML response from the server and formats it for display
     * @param siteUrl    - URL of server.
     * @return            - Returns text string.
     */
    private static String GetText( String siteUrl){
        String time = "";
        String intTemp = "";
        String extTemp = "";
        String text = "";
        Boolean success = true;

        Log.d("WSWidget" ,"Text in" + text);
        try {
            // Get the xml from the server specified in siteUrl
            URL url = new URL(siteUrl);
           
            // Create a factory that will take the xml and turn it into
            //a DOM (Document Object Model)
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
           
            // Parse the stream of xml returned from the server to create the DOM in doc
            Document doc = dBuilder.parse(new InputSource(url.openStream()));
            doc.getDocumentElement().normalize();
           
            // Get the contents of the tags we want to display
            time = getTagValue("time", doc);
            intTemp = getTagValueTemp("int", doc);
            extTemp = getTagValueTemp("ext", doc);


        } catch (Exception e){
            // There's a lot that can go wrong here with the internet connection
            // so we catch exceptions and add them to the system log.
            // This is viewable while the Android device is connected to the
            // pc using the ddms tool supplied with the sdk.
            success = false;
            Log.d("WSWidget" ,e.toString());
        }
       
        // Build the text string to display
        if (success){
            Log.d("WSWidget" ,"Succses got xml, changing text");
            text = time;
            text += "n";
            text += "Int Temp = ";
            text += intTemp;
            text += "cn";
            text += "Ext Temp = ";
            text += extTemp;
            text += "c";
        } else {
            text = "Weatherstation unreachablenCheck connections";
       
        }
        Log.d("WSWidget" ,"Text out" + text);
        // Return the text
        // If the fetch of the xml was unsuccessful keep the old text contents
        return text; // Return the text
    }

}

icon.png
widget_bg.9.png

Application icon as seen in the list of widgets when you add it to the home screen and .9.png special scalable background image with information on how it can be rotated or stretched.

icon.png                                                    widget_bg.9.png 

main.xml

layout of the contents of the widget. In this case a LinearLayout with two TextViews. One for the title and the second displaying the data formated from the xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/widget_bg">
    <TextView android:id="@+id/title"
        android:layout_width="fill_parent"
        android:layout_height="25px"
        android:text="@string/title"
        android:background="#ff78a2ed"
        android:gravity="center"
        android:layout_gravity="center_horizontal"
        android:textColor="@android:color/black"
        android:clickable="true" />
    <TextView android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="@string/greeting"
        android:gravity="left"
        android:textColor="@android:color/black" />
</LinearLayout>

strings.xml

Externalised text such as the title, application name and any other text commonly used in the widget.
I haven't put everything in here as I didn't get round to it. but any text needed in the application can go in here making it easy to implement localisation with different versions for multiple languages.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="greeting">Weather Station</string>
    <string name="refresh">Refresh</string>
    <string name="title">Weather Station</string>
    <string name="app_name">WSWidget</string>
    <string name="widget_name">Arduino Weather Station</string>
</resources>

widget.xml

Xml file defining the properties of the widget such as size and refresh rate.
There is a tight set of style guides for widgets to adhere to. So that your widget will fit the home screen properly and sit next to other widgets comfortably making the most of the space.

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="146dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="3600000"
    android:initialLayout="@layout/main"
    />

Theres quite a lot that can be done to improve the widget.
The code is far from perfect but then Im just dipping my toe into Java and while it is very similar to other languages such as C# that Ive used in the past, Im still wading my way through the reference material to get the hang of the code styles and techniques specific to Java. So Ill keep fiddling with it as I learn more and have a go at extending it. It would be nice to set the URL and refresh rates from a preferences screen and maybe have a button to launch a browser with the full web page showing all the data from my arduino project.

I trawled all over the internet looking at examples and tutorials.
I suggest a start with:

The SDK documentation, reference and tutorials. Very good to get you going.
Android developers forums. Lots of posts to search and good response to sensible questions.
Hello, Android by Ed Burnette from The Pragmatic Programmers series.
Links are here scratchpad.thisandthose.org/scratchpad/links/index.php