Stap 7: ZONNEPANEEL toezicht – ANDROID APP (2)
DE APP WIDGET
App widgets zijn kleine vensters die kunnen worden geplaatst op de Androïde huisscherm. Ze kunnen worden gebruikt om te laten zien van informatie of gegevens van de toepassing op het huisscherm zonder de toepassing die wordt uitgevoerd. Ik gebruik deze hier te hebben van de drie belangrijkste waarden (verbruikte vermogen, geëxporteerde of geïmporteerde macht en geproduceerde macht) altijd zichtbaar wanneer ik mijn telefoon of tablet gebruik.
Widget van de app maakt gebruik van eigen timer
/** Intent for broadcast message to update widgets */ Intent startIntent = new Intent(SPwidget.SP_WIDGET_UPDATE); /** Pending intent for broadcast message to update widgets */ PendingIntent pendingIntent = PendingIntent.getBroadcast( context, 2701, startIntent, PendingIntent.FLAG_CANCEL_CURRENT); /** Alarm manager for scheduled widget updates */ AlarmManager alarmManager = (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), alarmTime, pendingIntent);
de gegevens bijwerken elke 1 minuut. De vernieuwde gegevens wordt getrokken uit de Arduino bord
/** String list with parts of the URL */ String[] ipValues = deviceIP.split("/"); /** String with the URL to get the data */ String urlString="http://"+ipValues[2]+"/data/get"; // URL to call /** Response from the spMonitor device or error message */ String resultToDisplay = ""; /** A HTTP client to access the spMonitor device */ OkHttpClient client = new OkHttpClient(); /** Solar power received from spMonitor device as minute average */ Float solarPowerMin = 0.0f; /** Consumption received from spMonitor device as minute average */ Float consPowerMin = 0.0f; /** Request to spMonitor device */ Request request = new Request.Builder() .url(urlString) .build(); if (request != null) { /** Response from spMonitor device */ Response response = client.newCall(request).execute(); if (response != null) { resultToDisplay = response.body().string(); } } // decode JSON if (Utilities.isJSONValid(resultToDisplay)) { /** JSON object containing result from server */ JSONObject jsonResult = new JSONObject(resultToDisplay); /** JSON object containing the values */ JSONObject jsonValues = jsonResult.getJSONObject("value"); solarPowerMin = Float.parseFloat(jsonValues.getString("S")); consPowerMin = Float.parseFloat(jsonValues.getString("C")); /** Double for the result of solar current and consumption used at 1min updates */ double resultPowerMin = solarPowerMin + consPowerMin; views.setTextViewText(R.id.tv_widgetRow1Value, String.format("%.0f", resultPowerMin) + "W"); views.setTextViewText(R.id.tv_widgetRow2Value, String.format("%.0f", Math.abs(consPowerMin)) + "W"); views.setTextViewText(R.id.tv_widgetRow3Value, String.format("%.0f", solarPowerMin) + "W"); if (consPowerMin > 0.0d) { views.setTextColor(R.id.tv_widgetRow2Value, context.getResources() .getColor(android.R.color.holo_red_light)); } else { views.setTextColor(R.id.tv_widgetRow2Value, context.getResources() .getColor(android.R.color.holo_green_light)); } }
met hulp van de okhttp-bibliotheek die is gemakkelijk te gebruiken functies om te communiceren via een netwerk.
DATABASEFUNCTIES
Ik gebruik een uitbreiding van de SQLiteOpenHelper gemakkelijk toegang krijgen tot een lokale database op de Android.
class DataBaseHelper extends SQLiteOpenHelper
De database wordt automatisch gegenereerd wanneer de toepassing is het de eerste keer openen
public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + "year INTEGER, month INTEGER, day INTEGER, hour INTEGER, minute INTEGER, " + "solar DOUBLE, cons DOUBLE, light LONG, " + "id INTEGER PRIMARY KEY AUTOINCREMENT);"); }
Alle noodzakelijke functies voor toegang tot de database worden uitgevoerd in de database helper klasse.
class DataBaseHelper extends SQLiteOpenHelper /** * Add an entry to the database * * db * pointer to database * recordLine * String with a record * format: yy,mm,dd,hh:mm,light,solar,consumption * e.g.: "15,08,13,13:54,35000,613.456,-120.22" */ public static void addDay(SQLiteDatabase db, String recordLine) /** * Read data of day "dayNumber" and returns the data as a cursor * * db * pointer to database * dayNumber * the day we want to read (1-31) * monthSelected * the month we want to read from * yearSelected * the year we want to read from * Cursor dayStamp * Cursor with all database entries matching with dayNumber * Entry per minute is * cursor[0] = year stamp * cursor[1] = month stamp * cursor[2] = day stamp * cursor[3] = hour stamp * cursor[4] = minute stamp * cursor[5] = sensor power * cursor[6] = consumed power * cursor[7] = light value */ public static Cursor getDay(SQLiteDatabase db, int dayNumber, int monthSelected, int yearSelected) /** * Get specific row values from the database, * e.g. all years stored in the database or * all months of a year stored in the database or * all days in a month of a year stored in the database * * db * pointer to database * requestField * requested row from db * "year" returns all year values found * "month" returns all month values found in year requestLimiterYear * "day" returns all day values found in month requestLimiterMonth * and year requestLimiterYear * requestLimiterMonth * limiter for request * unused if requestField is "year" * unused if requestField is "month" * month if requestField is "day" * requestLimiterYear * limiter for request * unused if requestField is "year" * year if requestField is "month" * year if requestField is "day" * * ArrayList * array list with all entries found */ public static ArrayList getEntries(SQLiteDatabase db, String requestField, int requestLimiterMonth, int requestLimiterYear) /** * Read data of day "dayNumber" and returns the data as a cursor * * db * pointer to database * Cursor dayStamp * Cursor with the data of the last row * Entry is * cursor[0] = year stamp * cursor[1] = month stamp * cursor[2] = day stamp * cursor[3] = hour stamp * cursor[4] = minute stamp * cursor[5] = sensor power * cursor[6] = consumed power * cursor[7] = light value */ public static Cursor getLastRow(SQLiteDatabase db)
DE TIJDSYNCHRONISATIE-SERVICE
De Arduino voegt elke minuut een verzameling van de gegevens in de database. Als de androïde toepassing wordt gestart, kan de hoeveelheid gegevens die moeten worden gesynchroniseerd worden vrij groot en lang duren om te synchroniseren. In te korten dat ik heb gemaakt een achtergrond service die eenmaal per dag heet om te synchroniseren van de databanken van de Arduino en het Android apparaat. Voor de synchronisatie heet de script-query.php op de Arduino.
/** A HTTP client to access the spMonitor device */OkHttpClient client = new OkHttpClient();/** String list with parts of the URL */ String[] ipValues = deviceIP.split("/"); /** URL to be called */ String urlString = "http://"+ipValues[2]+"/sd/spMonitor/query.php"; // URL to call// Check for last entry in the local database /** Instance of DataBaseHelper */ DataBaseHelper dbHelper = new DataBaseHelper(intentContext); /** Instance of data base */ SQLiteDatabase dataBase = dbHelper.getReadableDatabase(); /** Cursor with data from database */ Cursor dbCursor = DataBaseHelper.getLastRow(dataBase); if (dbCursor.getCount() != 0) { // local database not empty, need to sync only missing dbCursor.moveToFirst(); int lastMinute = dbCursor.getInt(4); int lastHour = dbCursor.getInt(3); int lastDay = dbCursor.getInt(2); urlString += "?date=" + dbCursor.getString(0); // add year urlString += "-" + ("00" + dbCursor.getString(1)).substring(dbCursor.getString(1).length()); // add month urlString += "-" + ("00" + String.valueOf(lastDay)) .substring(String.valueOf(lastDay).length()); // add day urlString += "-" + ("00" + String.valueOf(lastHour)) .substring(String.valueOf(lastHour).length()); // add hour urlString += ":" + ("00" + String.valueOf(lastMinute)) .substring(String.valueOf(lastMinute).length()); // add minute urlString += "&get=all"; } // else {} local database is empty, need to sync all data dbCursor.close(); dataBase.close(); dbHelper.close(); // Make call only if valid url is given if (!urlString.startsWith("No")) { /** Request to spMonitor device */ Request request = new Request.Builder() .url(urlString) .build(); if (request != null) { /** Response from spMonitor device */ Response response = client.newCall(request).execute(); if (response != null) { resultData = response.body().string(); if (Utilities.isJSONValid(resultData)) { /** JSON array with the data received from spMonitor device */ JSONArray jsonFromDevice = new JSONArray(resultData); /** Instance of DataBaseHelper */ dbHelper = new DataBaseHelper(intentContext); /** Instance of data base */ dataBase = dbHelper.getWritableDatabase(); // Get received data into local database // skip first data record from device, it is already in the database for (int i=1; i<jsonFromDevice.length();i++) { // JSONObject with a single record JSONObject jsonRecord = jsonFromDevice.getJSONObject(i); String record = jsonRecord.getString("d"); record = record.replace("-",","); record += ","+jsonRecord.getString("l"); record += ","+jsonRecord.getString("s"); record += ","+jsonRecord.getString("c"); DataBaseHelper.addDay(dataBase, record); } dataBase.close(); dbHelper.close(); } } } }
Opnieuw is de okhttp library gebruikt voor toegang tot de Arduino board.
DE SERVICE OPNIEUW OPSTARTEN
Het is noodzakelijk de timers voor de app widget updates en de dagelijkse synchronisatie opnieuw starten nadat het Android apparaat aanstaat of rebootet was. Dit wordt gedaan in de AutoStart-klasse, die wordt aangeroepen door de Android OS, telkens wanneer het apparaat wordt gestart of rebootet was.
void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { /** Access to shared preferences of app widget */ SharedPreferences wPrefs = context.getSharedPreferences("spMonitor",0); if (BuildConfig.DEBUG) Log.d("spMonitor Autostart","Widget number = "+wPrefs.getInt("wNums",0)); if (wPrefs.getInt("wNums",0) != 0) { if (BuildConfig.DEBUG) Log.d("spMonitor Autostart","activating widget timer"); /** Update interval in ms */ int alarmTime = 60000; /** Intent for broadcast message to update widgets */ Intent widgetIntent = new Intent(SPwidget.SP_WIDGET_UPDATE); /** Pending intent for broadcast message to update widgets */ PendingIntent pendingWidgetIntent = PendingIntent.getBroadcast( context, 2701, widgetIntent, PendingIntent.FLAG_CANCEL_CURRENT); /** Alarm manager for scheduled widget updates */ AlarmManager alarmManager = (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, alarmTime, pendingWidgetIntent); // Start service to register ScreenReceiverService context.startService(new Intent(context, ScreenReceiverService.class)); } /** Calendar instance to setup daily sync */ Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 5); // trigger at 1am calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); /** Pending intent for daily sync */ PendingIntent pi = PendingIntent.getService(context, 2702, new Intent(context, SyncService.class),PendingIntent.FLAG_UPDATE_CURRENT); /** Alarm manager for daily sync */ AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pi); } }
De AutoStart-klasse controleert als er widgets app actief en start de timer van de update indien nodig. De timer voor de dagelijkse synchronisatie wordt gestart elke keer.
Dat is het voor de Android applicatie. Ik ingaan niet op details hier, omdat de broncode vrij groot is. Maar als u geïnteresseerd bent kunt u de hele broncode van mijn Github repository.
Hoofd over naar de laatste post van deze serie. Er vindt u een lijst met hardwareonderdelen die ik gebruikte, ontwikkeling IDE gebruikt voor de Arduino en Android Softwareontwikkeling en de nodige bibliotheken gebruikt in de toepassingen.