diff --git a/src/com/ineptech/magicmirror/modules/ForecastModule.java b/src/com/ineptech/magicmirror/modules/ForecastModule.java index da77465..be9d39a 100644 --- a/src/com/ineptech/magicmirror/modules/ForecastModule.java +++ b/src/com/ineptech/magicmirror/modules/ForecastModule.java @@ -16,16 +16,21 @@ import android.content.Context; import android.graphics.Color; +import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.text.InputType; +import android.text.style.ImageSpan; import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.view.View; import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; +import android.util.Log; import com.ineptech.magicmirror.MainApplication; +import com.ineptech.magicmirror.R; import com.ineptech.magicmirror.Utils; public class ForecastModule extends Module { @@ -33,33 +38,82 @@ public class ForecastModule extends Module { String apikey = "Go_to_darksky.net/dev_and_register_for_apikey_and_enter_here"; double latitude, latitude_def = 45.5200; double longitude, longitude_def = -122.6819; + private static final long timeBetweenCalls = 10 * 60 * 1000; // 10 minutes + long lastRan = 0; + int consecFails = 0; String cast = ""; Boolean useCelsius = false; CheckBox cbCelsius; + ImageSpan isRain, isClearDay, isClearNight, isCloudy, isFog, isPartlyCloudyDay, isPartlyCloudyNight, isSleet, isSnow, isWind; + public ForecastModule(Context context) { super("Weather"); desc = "Shows today's temperatures: \"current (high | low)\" as reported by forecast.io. " - + "This will not work until you go to darksky.net/dev and register to get a (free) api key " - + "and enter it in to the box below. To do so, go to darksky.net/dev and click on Register. " + + "This will not work until you go to forecast.io and register to get a (free) api key " + + "and enter it in to the box below. To do so, visit developer.forecast.io and click on Register. " + "Then enter your latitude and longitude (be sure to get the signs right). I hope to add " + "snow/rain/etc icons at some point, haven't gotten to it yet..."; defaultTextSize = 72; - sampleString = "100° (90° | 110°)"; - hasRegularUpdates = true; - defaultUpdateFrequency = 10; + sampleString = "100� (90� | 110�)"; loadConfig(); } - + + public void setIconSizes() { + Context context = MainApplication.getContext(); + int iconSize = Math.round(tv.getTextSize()); + + //make each icon + Drawable iconClearDay = context.getResources().getDrawable(R.drawable.clear_day); + iconClearDay.setBounds(0,0,iconSize,iconSize); + isClearDay = new ImageSpan(iconClearDay); + + Drawable iconClearNight = context.getResources().getDrawable(R.drawable.clear_night); + iconClearNight.setBounds(0,0,iconSize,iconSize); + isClearNight = new ImageSpan(iconClearNight); + + Drawable iconCloudy = context.getResources().getDrawable(R.drawable.cloudy); + iconCloudy.setBounds(0,0,iconSize,iconSize); + isCloudy = new ImageSpan(iconCloudy); + + Drawable iconFog = context.getResources().getDrawable(R.drawable.fog); + iconFog.setBounds(0,0,iconSize,iconSize); + isFog = new ImageSpan(iconFog); + + Drawable iconPartlyCloudyDay = context.getResources().getDrawable(R.drawable.partly_cloudy_day); + iconPartlyCloudyDay.setBounds(0,0,iconSize,iconSize); + isPartlyCloudyDay = new ImageSpan(iconPartlyCloudyDay); + + Drawable iconPartlyCloudyNight = context.getResources().getDrawable(R.drawable.partly_cloudy_night); + iconPartlyCloudyNight.setBounds(0,0,iconSize,iconSize); + isPartlyCloudyNight = new ImageSpan(iconPartlyCloudyNight); + + Drawable iconRain = context.getResources().getDrawable(R.drawable.rain); + iconRain.setBounds(0,0,iconSize,iconSize); + isRain = new ImageSpan(iconRain); + + Drawable iconSleet = context.getResources().getDrawable(R.drawable.sleet); + iconSleet.setBounds(0,0,iconSize,iconSize); + isSleet = new ImageSpan(iconSleet); + + Drawable iconSnow = context.getResources().getDrawable(R.drawable.snow); + iconSnow.setBounds(0,0,iconSize,iconSize); + isSnow = new ImageSpan(iconSnow); + + Drawable iconWind = context.getResources().getDrawable(R.drawable.wind); + iconWind.setBounds(0,0,iconSize,iconSize); + isWind = new ImageSpan(iconWind); + } public void update() { if (Utils.isNewDay()) { - consecFails = 0; // try to retry every day in case of temporary network failures - tv.setText(""); // clear old text + consecFails = 0; // retry every day in case of temporary network failures + new ForecastTask(this).execute(); } + if (consecFails > 9) { tv.setText(""); tv.setVisibility(TextView.GONE); - } else if (Calendar.getInstance().getTimeInMillis() > (lastRan + timeBetweenUpdates)) { + } else if (Calendar.getInstance().getTimeInMillis() > (lastRan + timeBetweenCalls)) { new ForecastTask(this).execute(); } } @@ -109,7 +163,7 @@ public void makeConfigLayout() { eLong.setBackgroundColor(Color.LTGRAY); holder.addView(eLong); TextView tv3 = new TextView(MainApplication.getContext()); - tv3.setText("Api key (get from darksky.net and copy-paste here): "); + tv3.setText("Api key (get from forecast.io and copy-paste here): "); holder.addView(tv3); eApikey = new EditText(MainApplication.getContext()); eApikey.setBackgroundColor(Color.LTGRAY); @@ -130,20 +184,46 @@ public void makeConfigLayout() { } void set() { - // TODO: add icons for snow and rain. To do so: - // First, update ___ to include a key word, e.g. "umbrella", to the forecast display test. - // Then, trandform it into an icon here with in this method with code like so: - // String keyword = "umbrella"; - // int i = cast.indexOf(keyword); - // if (i >= 0) - // span.setSpan(isUmbrella, i, i+keyword.length, 0); + + tv.setVisibility(TextView.VISIBLE); + + setIconSizes(); SpannableString span = new SpannableString(cast); SpannableStringBuilder builder = new SpannableStringBuilder(); + + //see if we have an icon that matches the icon in our string + String rain = "rain", clearDay = "clear-day", clearNight = "clear-night", cloudy = "cloudy", fog = "fog", partlyCloudyDay = "partly-cloudy-day", partlyCloudyNight = "partly-cloudy-night", sleet="sleet", snow="snow", wind="wind"; + + int r = cast.indexOf(rain), cD=cast.indexOf(clearDay), cN=cast.indexOf(clearNight), c=cast.indexOf(cloudy), f=cast.indexOf(fog), pCD=cast.indexOf(partlyCloudyDay), pCN=cast.indexOf(partlyCloudyNight),sl=cast.indexOf(sleet),s=cast.indexOf(snow),w=cast.indexOf(wind); + + //if we do, change the word to the icon png + if (r>=0) + span.setSpan(isRain, r, r+rain.length(), 0); + else if (cD>=0) + span.setSpan(isClearDay, cD, cD+clearDay.length(), 0); + else if (cN>=0) + span.setSpan(isClearNight, cN, cN+clearNight.length(), 0); + else if (pCD>=0) + span.setSpan(isPartlyCloudyDay, pCD, pCD+partlyCloudyDay.length(), 0); + else if (pCN>=0) + span.setSpan(isPartlyCloudyNight, pCN, pCN+partlyCloudyNight.length(), 0); + else if (c>=0) + span.setSpan(isCloudy, c, c+cloudy.length(), 0); + else if (f>=0) + span.setSpan(isFog, f, f+fog.length(), 0); + else if (sl>=0) + span.setSpan(isSleet, sl, sl+sleet.length(), 0); + else if (s>=0) + span.setSpan(isSnow, s, s + snow.length(), 0); + else if (w>=0) + span.setSpan(isWind, w, w + wind.length(), 0); + else span.setSpan("", cast.lastIndexOf("°)"), cast.lastIndexOf("°)"), 0); + builder.append(span); tv.setText(builder); - consecFails = 0; lastRan = Calendar.getInstance().getTimeInMillis(); + } } @@ -151,18 +231,16 @@ void set() { class ForecastTask extends AsyncTask { private ForecastModule module; - - String forecastDomain = "https://api.darksky.net"; String sampleResponse = "{\"latitude\":45.52,\"longitude\":122.6819,\"timezone\":\"Asia/Harbin\",\"offset\":8,\"currently\":{\"time\":1449289114,\"summary\":\"Clear\",\"icon\":\"clear-day\",\"precipType\":\"snow\",\"temperature\":22.35,\"apparentTemperature\":11.63,\"dewPoint\":2.76,\"humidity\":0.42,\"windSpeed\":10.22,\"windBearing\":304,\"visibility\":10,\"cloudCover\":0,\"pressure\":1026.52},\"daily\":{\"data\":[{\"time\":1449244800,\"summary\":\"Clear throughout the day.\",\"icon\":\"clear-day\",\"sunriseTime\":1449270846,\"sunsetTime\":1449302882,\"moonPhase\":0.8,\"precipType\":\"snow\",\"temperatureMin\":10.48,\"temperatureMinTime\":1449320400,\"temperatureMax\":22.73,\"temperatureMaxTime\":1449291600,\"apparentTemperatureMin\":4.25,\"apparentTemperatureMinTime\":1449316800,\"apparentTemperatureMax\":12.86,\"apparentTemperatureMaxTime\":1449291600,\"dewPoint\":3.95,\"humidity\":0.58,\"windSpeed\":5.86,\"windBearing\":292,\"visibility\":8.33,\"pressure\":1024.88}]},\"flags\":{\"sources\":[\"isd\"],\"isd-stations\":[\"508440-99999\",\"509490-99999\",\"540260-99999\",\"540490-99999\"],\"units\":\"us\"}}"; - String sampleForecastURL = forecastDomain + "/forecast/api_key_goes_here/45.5200,-122.6819,2015-12-04T20:18:34-0800?units=us&exclude=minutely,hourly"; - String sample2 = forecastDomain + "/forecast/apikey/37.8267,-122.4233"; - + String sampleForecastURL = "https://api.forecast.io/forecast/api_key_goes_here/45.5200,-122.6819,2015-12-04T20:18:34-0800?units=us&exclude=minutely,hourly"; + public ForecastTask(ForecastModule _module) { module = _module; } @Override protected String doInBackground(Void... params) { + if (Utils.debug) return sampleResponse; @@ -170,31 +248,42 @@ protected String doInBackground(Void... params) { HttpContext localContext = new BasicHttpContext(); String forecastTime = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); forecastTime += "T" + new SimpleDateFormat("HH:mm:ss").format(new Date()); - String forecastURL = forecastDomain+ "/forecast/"+module.apikey+"/"+module.latitude+","+module.longitude+","; + + String forecastURL = "https://api.forecast.io/forecast/"+module.apikey+"/"+module.latitude+","+module.longitude+","; + + String forecastParams = "?exclude=minutely,hourly"; + /****************************/ + //String forecastParams = "?exclude=minutely,hourly,daily"; + /****************************/ if (module.useCelsius) forecastParams += "&units=si"; + //String wholeURL = forecastURL+forecastParams; String wholeURL = forecastURL+forecastTime+forecastParams; HttpGet httpGet = new HttpGet(wholeURL); String text = null; + try { HttpResponse response = httpClient.execute(httpGet, localContext); HttpEntity entity = response.getEntity(); text = Utils.getASCIIContentFromEntity(entity); + } catch (Exception e) { module.consecFails++; return e.getLocalizedMessage(); } return text; + } protected void onPostExecute(String results) { + if (results!=null) { - // TODO: improve parsing of the forecast results try { System.out.println(results); module.cast = parseForecast(results); module.set(); + } catch (Exception e) { module.consecFails++; e.printStackTrace(); @@ -205,26 +294,29 @@ protected void onPostExecute(String results) { } String temp = "", high = "", low = "", icon = ""; + String parseForecast (String s) throws Exception { - String re = ".*temperature\":([\\-0-9\\.]+),.*temperatureMin\":([\\-0-9\\.]+),.*temperatureMax\":([\\-0-9\\.]+),.*"; + //search for icon, temperature, temperature high, temperature low + //String re = ".*icon\":\"([\\-a-zA-Z]+)\",.*temperature\":([\\-0-9\\.]+),.*"; + String re = ".*icon\":\"([\\-a-zA-Z]+)\",.*temperature\":([\\-0-9\\.]+),.*temperatureMin\":([\\-0-9\\.]+),.*temperatureMax\":([\\-0-9\\.]+),.*"; + Pattern r = Pattern.compile(re); Matcher m = r.matcher(s); if (m.find()) { - temp = m.group(1); - low = m.group(2); - high = m.group(3); + icon = m.group(1); + temp = m.group(2); + low = m.group(3); + high = m.group(4); } + int ftemp = Math.round(Float.parseFloat(temp)); int flow = Math.round(Float.parseFloat(low)); int fhigh = Math.round(Float.parseFloat(high)); - String cast = ftemp +"° ("+fhigh+"° | "+flow+"°)"; - - // TODO: Check for weather alerts and display something suitable? - if (s.contains("Alert")) { - // do something... - } - // TODO: Add icons for snow, rain, etc - + + //create string with icon word, temperature, high, low + //String cast = icon + " " + ftemp +"°"; + String cast = icon + " " + ftemp +"° ("+fhigh+"° | "+flow+"°)"; + return cast; } }