/* Copyright (C) 2011-2012, Dirk Trossen, airs@dirk-trossen.de This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation as version 2.1 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.airs.handlers; import java.net.URL; import java.util.concurrent.Semaphore; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import com.airs.R; import com.airs.helper.Waker; import com.airs.platform.HandlerManager; import com.airs.platform.History; import com.airs.platform.SensorRepository; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.Handler; import android.os.Message; /** * Class to read weather related sensors, specifically the VT, VF, VH, VC, VW, VI sensor * @see Handler */ public class WeatherHandler implements com.airs.handlers.Handler, Runnable { private static final int INIT_GPS = 1; private static final int KILL_GPS = 2; private static final int INIT_RECEIVER = 4; private Context nors; private boolean weather_enabled; private int temperature_c, temperature_f = 0; private int humidity = 0; private String condition; private String wind; // XML stuff private ExampleHandler myWeatherHandler; private XMLReader XMLreader; private SAXParser sp; // threading for XML polling private Thread runnable = null; private boolean running = true; private boolean shutdown = false; private int polltime = 10000; private int updatemeter = 1000; // location stuff private LocationManager manager; private LocationListener mReceiver; private double Longitude = 0, Latitude = 0; private boolean startedLocation = false; private boolean first_fix = true, movedAway = false, connectivity_listener = false; private Location old_location; // connectvity stuff private ConnectivityManager cm; // semaphores for multi-threading private Semaphore location_semaphore = new Semaphore(1); private Semaphore temp_c_semaphore = new Semaphore(1); private Semaphore temp_f_semaphore = new Semaphore(1); private Semaphore hum_semaphore = new Semaphore(1); private Semaphore cond_semaphore = new Semaphore(1); private Semaphore wind_semaphore = new Semaphore(1); private Semaphore info_semaphore = new Semaphore(1); private Semaphore connectivity_semaphore= new Semaphore(1); // boolean for which element is parsed private boolean temp_c_element; private boolean temp_f_element; private boolean hum_element; private boolean cond_element; private boolean wind_element; /** * Sleep function * @param millis */ private void sleep(long millis) { Waker.sleep(millis); } private void wait(Semaphore sema) { try { sema.acquire(); } catch(Exception e) { } } /** * Method to acquire sensor data * Here, we start the thread for retrieving the weather and switch on the GPS, if not done before * @param sensor String of the sensor symbol * @param query String of the query to be fulfilled - not used here * @see com.airs.handlers.Handler#Acquire(java.lang.String, java.lang.String) */ public byte[] Acquire(String sensor, String query) { byte[] readings = null; String readingsString; // are we shutting down? if (shutdown == true) return null; // no thread started yet? if (runnable == null) { runnable = new Thread(this); runnable.start(); // initialise receiver for connectivity change Message msg = mHandler.obtainMessage(INIT_RECEIVER); mHandler.sendMessage(msg); } // temperature in Celcius if(sensor.compareTo("VT") == 0) { // wait for semaphore wait(temp_c_semaphore); readings = new byte[6]; readings[0] = (byte)sensor.charAt(0); readings[1] = (byte)sensor.charAt(1); readings[2] = (byte)((temperature_c>>24) & 0xff); readings[3] = (byte)((temperature_c>>16) & 0xff); readings[4] = (byte)((temperature_c>>8) & 0xff); readings[5] = (byte)(temperature_c & 0xff); return readings; } // temperature in Farenheit if(sensor.compareTo("VF") == 0) { // wait for semaphore wait(temp_f_semaphore); readings = new byte[6]; readings[0] = (byte)sensor.charAt(0); readings[1] = (byte)sensor.charAt(1); readings[2] = (byte)((temperature_f>>24) & 0xff); readings[3] = (byte)((temperature_f>>16) & 0xff); readings[4] = (byte)((temperature_f>>8) & 0xff); readings[5] = (byte)(temperature_f & 0xff); return readings; } // Humidity if(sensor.compareTo("VH") == 0) { // wait for semaphore wait(hum_semaphore); readings = new byte[6]; readings[0] = (byte)sensor.charAt(0); readings[1] = (byte)sensor.charAt(1); readings[2] = (byte)((humidity>>24) & 0xff); readings[3] = (byte)((humidity>>16) & 0xff); readings[4] = (byte)((humidity>>8) & 0xff); readings[5] = (byte)(humidity & 0xff); return readings; } // Conditions if(sensor.compareTo("VC") == 0) { // wait for semaphore wait(cond_semaphore); readingsString = new String("VC" + condition); return readingsString.getBytes(); } // Wind Conditions if(sensor.compareTo("VW") == 0) { // wait for semaphore wait(wind_semaphore); readingsString = new String("VW" + wind); return readingsString.getBytes(); } // All information together if(sensor.compareTo("VI") == 0) { // wait for semaphore wait(info_semaphore); readingsString = new String("VI" + Double.toString(Latitude) + ":" + Double.toString(Longitude) + ":" + Integer.toString(temperature_c) + ":" + Integer.toString(temperature_f) + ":" + Integer.toString(humidity) + ":" + condition + ":" + wind); return readingsString.getBytes(); } return null; } /** * Method to share the last value of the given sensor * @param sensor String of the sensor symbol to be shared * @return human-readable string of the last sensor value * @see com.airs.handlers.Handler#Share(java.lang.String) */ public String Share(String sensor) { // temperature in Celcius if(sensor.compareTo("VT") == 0) return "The current temperature is " + String.valueOf(temperature_c) + " C" + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; // temperature in Farenheit if(sensor.compareTo("VF") == 0) return "The current temperature is " + String.valueOf(temperature_f) + " F" + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; // Humidity if(sensor.compareTo("VH") == 0) return "The current humidity is " + String.valueOf(humidity) + " %" + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; // Conditions if(sensor.compareTo("VC") == 0) return "The current weather condition is " + condition + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; // Wind Conditions if(sensor.compareTo("VW") == 0) return "The current wind condition is " + wind + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; // All information together if(sensor.compareTo("VI") == 0) return "The current weather condition is " + String.valueOf(temperature_c) + " C (" + String.valueOf(temperature_f) + " F) with a humidity of " + String.valueOf(humidity) + " %, conditions '" + condition + "' and " + wind + " wind conditions!" + "\nRecorded at " + "http://maps.google.com?q=(" + String.valueOf(Latitude) + "," + String.valueOf(Longitude) + ")"; return null; } /** * Method to view historical chart of the given sensor symbol * @param sensor String of the symbol for which the history is being requested * @see com.airs.handlers.Handler#History(java.lang.String) */ public void History(String sensor) { // temperature in Celcius if(sensor.compareTo("VT") == 0) History.timelineView(nors, "Temperature [C]", "VT"); // temperature in Farenheit if(sensor.compareTo("VF") == 0) History.timelineView(nors, "Temperature [F]", "VF"); // Humidity if(sensor.compareTo("VH") == 0) History.timelineView(nors, "Humidity [&]", "VH"); // Weather info if(sensor.compareTo("VI") == 0) History.mapView(nors, "Weather on the map", "VI"); } /** * Method to discover the sensor symbols support by this handler, if the weather is supported * As the result of the discovery, appropriate {@link com.airs.platform.Sensor} entries will be added to the {@link com.airs.platform.SensorRepository} * @see com.airs.handlers.Handler#Discover() * @see com.airs.platform.Sensor * @see com.airs.platform.SensorRepository */ public void Discover() { if (weather_enabled == true) { SensorRepository.insertSensor(new String("VT"), new String("C"), nors.getString(R.string.VT_d), nors.getString(R.string.VT_e), new String("int"), 0, -50, 100, true, 0, this); SensorRepository.insertSensor(new String("VF"), new String("F"), nors.getString(R.string.VF_d), nors.getString(R.string.VF_e), new String("int"), 0, -50, 100, true, 0, this); SensorRepository.insertSensor(new String("VH"), new String("%"), nors.getString(R.string.VH_d), nors.getString(R.string.VH_e), new String("int"), 0, 0, 100, true, 0, this); SensorRepository.insertSensor(new String("VC"), new String("txt"), nors.getString(R.string.VC_d), nors.getString(R.string.VC_e), new String("str"), 0, 0, 1, false, 0, this); SensorRepository.insertSensor(new String("VW"), new String("txt"), nors.getString(R.string.VW_d), nors.getString(R.string.VW_e), new String("str"), 0, 0, 1, false, 0, this); SensorRepository.insertSensor(new String("VI"), new String("txt"), nors.getString(R.string.VI_d), nors.getString(R.string.VI_e), new String("str"), 0, 0, 1, true, 0, this); } } /** * run weather retrieval in separate thread * @see java.lang.Runnable#run() */ public void run() { InputSource input; int sleeptime = polltime; long started = 0, ended = 0; boolean startReading = true; boolean firstReading = true; // run until destroyed while(running==true) { // sleep until next reading if it is not the first reading if (firstReading == false) sleep(sleeptime); // if killed during sleeping then return now! if (running == false) return; firstReading = false; // store timestamp when start reading if (startReading == true) { started = System.currentTimeMillis(); startReading = false; } // only do location check etc if there's any connectivity to retrieve the weather! wait(connectivity_semaphore); // if killed during sleeping then return now! if (running == false) return; // get current weather conditions try { // try to get current location if (manager!=null) { // send message to handler thread to start GPS Message msg = mHandler.obtainMessage(INIT_GPS); mHandler.sendMessage(msg); // wait for update wait(location_semaphore); // now unregister again -> saves power msg = mHandler.obtainMessage(KILL_GPS); mHandler.sendMessage(msg); } // if we moved, try to get weather if (movedAway == true && running == true) { // request update for that long,lat pair! // URL url = new URL("http://www.google.com/ig/api?weather=,,," + Integer.toString((int)(Latitude * 1000000)) + "," + Integer.toString((int)(Longitude * 1000000))); // URL url = new URL("http://free.worldweatheronline.com/feed/weather.ashx?q="+Double.toString(Latitude) + "," + Double.toString(Longitude) + "&format=xml&num_of_days=1&key=0f86de2f9c161417123108"); URL url = new URL("http://api.worldweatheronline.com/free/v1/weather.ashx?q="+Double.toString(Latitude) + "," + Double.toString(Longitude) + "&format=xml&num_of_days=1&key=st4dghppmrfbtcrhwggn76u8"); // 51914540,900690"); /* Parse the xml-data from our URL. */ input = new InputSource(url.openStream()); XMLreader.parse(input); input = null; } // get current timestamp to see how long the weather reading took all along ended = System.currentTimeMillis(); // if there's still something to wait until next weather reading, do so if ((int)(ended - started) < polltime) sleeptime = polltime - (int)(ended - started); else sleeptime = 1; // otherwise start right away // store start timestamp the next time around startReading = true; } catch(Exception e) { // try again to read/locate in 15 secs sleeptime = 15000; } } } /** * Constructor, allocating all necessary resources for the handler * Here, it's arming the semaphore and retrieving the reference to the XMLParser as well as a reference to the {@link android.location.LocationManager} * @param airs Reference to the calling {@link android.content.Context} */ public WeatherHandler(Context airs) { this.nors = airs; // read polltime from preferences polltime = HandlerManager.readRMS_i("WeatherHandler::WeatherPoll", 10) * 1000 * 60; updatemeter = HandlerManager.readRMS_i("WeatherHandler::LocationUpdate", 1000); try { /* Get a SAXParser from the SAXPArserFactory. */ SAXParserFactory spf = SAXParserFactory.newInstance(); sp = spf.newSAXParser(); /* Get the XMLReader of the SAXParser we created. */ XMLreader = sp.getXMLReader(); /* Create a new ContentHandler and apply it to the XML-Reader*/ myWeatherHandler = new ExampleHandler(); XMLreader.setContentHandler(myWeatherHandler); // arm semaphores wait(temp_c_semaphore); wait(temp_f_semaphore); wait(hum_semaphore); wait(cond_semaphore); wait(wind_semaphore); wait(info_semaphore); wait(location_semaphore); wait(connectivity_semaphore); // get location manager manager = (LocationManager)this.nors.getSystemService(Context.LOCATION_SERVICE); if (manager != null) mReceiver = new LocationReceiver(); // get connectivity manager for checking connectivity cm = (ConnectivityManager)this.nors.getSystemService(Context.CONNECTIVITY_SERVICE); // everything ok to be used weather_enabled = true; } catch(Exception e) { // something went wrong weather_enabled = false; } } /** * Method to release all handler resources * Here, we unregister the location receiver, shut down the retrieval thread, unregister the connectivity receiver and release all semaphores * @see com.airs.handlers.Handler#destroyHandler() */ public void destroyHandler() { // signal shutdown shutdown = true; // release all semaphores for unlocking the Acquire() threads temp_c_semaphore.release(); temp_f_semaphore.release(); hum_semaphore.release(); cond_semaphore.release(); wind_semaphore.release(); info_semaphore.release(); // signal thread to close down running = false; // release connectivity semaphore connectivity_semaphore.release(); // release location semaphore location_semaphore.release(); // kill thread if (runnable != null) runnable.interrupt(); if (startedLocation == true) manager.removeUpdates(mReceiver); if (connectivity_listener == true) nors.unregisterReceiver(ConnectivityReceiver); // remove all messages mHandler.removeMessages(INIT_GPS); mHandler.removeMessages(INIT_RECEIVER); mHandler.removeMessages(KILL_GPS); } // The Handler that gets information back from the other threads, initializing GPS // We use a handler here to allow for the Acquire() function, which runs in a different thread, to issue an initialization of the GPS // since requestLocationUpdates() can only be called from the main Looper thread!! private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case INIT_GPS: // are we shutting down? if (shutdown == true) return; // request location updates if (manager!=null) { try { manager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, (float)0, mReceiver); } catch(Exception e) { } startedLocation = true; } break; case KILL_GPS: // request location updates if (manager!=null && startedLocation == true) { manager.removeUpdates(mReceiver); startedLocation = false; } break; case INIT_RECEIVER: IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); nors.registerReceiver(ConnectivityReceiver, intentFilter); connectivity_listener = true; break; default: break; } } }; private class ExampleHandler extends DefaultHandler { private boolean current_cond = false; @Override public void startDocument() throws SAXException { current_cond = false; } /** Gets to be called on opening tags like: * <tag> * Can provide attribute(s), when xml was like: * <tag attribute="attributeValue">*/ @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { // no name provided? if (localName == null) return; // check for entering current conditions part of message if (localName.equals("current_condition")) { current_cond = true; } if (localName.equals("temp_C") && current_cond==true) temp_c_element = true; if (localName.equals("temp_F") && current_cond==true) temp_f_element = true; if (localName.equals("humidity") && current_cond==true) hum_element = true; if (localName.equals("weatherDesc") && current_cond==true) cond_element = true; if (localName.equals("windspeedMiles") && current_cond==true) wind_element = true; } public void endElement(String namespaceURI, String localName, String qName) throws SAXException { // no name provided? if (localName == null) return; if (localName.equals("current_condition")) { current_cond = false; // signal possible Acquire() thread since we should have read everything now! info_semaphore.release(); } } @Override public void characters(char ch[], int start, int length) { // no proper length provided? if (length == 0) return; String chars = new String(ch, start, length); chars = chars.trim(); if (temp_c_element == true) { temperature_c = Integer.parseInt(chars); // signal possible Acquire() thread temp_c_semaphore.release(); // clear element flag temp_c_element = false; } if (temp_f_element == true) { temperature_f = Integer.parseInt(chars); // signal possible Acquire() thread temp_f_semaphore.release(); // clear element flag temp_f_element = false; } if (hum_element == true) { humidity = Integer.parseInt(chars); // signal possible Acquire() thread hum_semaphore.release(); // clear element flag hum_element = false; } if (cond_element == true) { condition = chars; // signal possible Acquire() thread cond_semaphore.release(); // clear element flag cond_element = false; } if (wind_element == true) { wind = chars + "mph"; // signal possible Acquire() thread wind_semaphore.release(); // clear element flag wind_element = false; } } } private final BroadcastReceiver ConnectivityReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); // When connectivity changed... if (ConnectivityManager.CONNECTIVITY_ACTION.compareTo(action) == 0) { // check connectivity NetworkInfo netInfo = cm.getActiveNetworkInfo(); if (netInfo != null) if (netInfo.isConnected() == true) connectivity_semaphore.release(); // release semaphore else connectivity_semaphore.drainPermits(); // drain semaphore return; } } }; private class LocationReceiver implements LocationListener { public void onLocationChanged(Location location) { if (location != null) { Longitude = location.getLongitude(); Latitude = location.getLatitude(); // shall we check for moving? if (updatemeter>0) { // is this the first fix? if (first_fix == false) { // have we moved? if (location.distanceTo(old_location)>updatemeter) { movedAway = true; old_location = location; } else movedAway = false; } else { first_fix = false; old_location = location; movedAway = true; } } else // we're always moving since we're always updating! movedAway = true; // now release the semaphores location_semaphore.release(); } } public void onProviderDisabled(String provider) { } public void onProviderEnabled(String provider) { } public void onStatusChanged(String provider, int status, Bundle extras) { } }; }