• Non ci sono risultati.

4.2 L’applicazione Android

4.2.1 Realizzazione dei meccanismi necessari

4.2.1.4 Sensor usage

Le applicazioni in esecuzione sul gateway potrebbero essere interessate a specifici sensori offerti dai vari device. Per questo motivo al fine di ridurre il numero di connessioni poco utili è importante cercare di filtrare i dispositivi in base ai sensori che possano fornire alle applicazioni informazioni che queste ultime ritengono interessanti: se un’applicazione in esecuzione su Kura offre soluzioni di domotica potrebbe non essere interessata ai dati di un giroscopio, e quindi un dispositivo che si connette a quel gateway potrebbe risultare inutile se non ha altri dati da offrire. È possibile realizzare questo filtro comunicando all’interno del messaggio di presentazione del device anche i sensori che mette a disposizione del gateway.

Per accedere ai sensori Android mette a disposizione un servizio di sistema, definito dalla classe SensorManager, che offre metodi per ottenere informazioni sui sensori disponibili e registrare dei listener su questi ultimi.

Ogni listener verrà notificato quando il sensore su cui è registrato aggiornerà i suoi dati con una nuova misurazione. L’utilizzo dei listener per inviare messaggi al broker, tuttavia, potrebbe non essere una solu- zione ottimale: la minima frequenza di aggiornamento di un sensore è di circa quattro aggiornamenti ogni

secondo, il che aumenterebbe il throughput, anche tenendo in considerazione che ogni dispositivo può met- tere a disposizione un gran numero di sensori. Si è pensato che una soluzione migliore possa essere quella di utilizzare il SensorListener per aggiornare una lista di rappresentazioni JSON di SensorEvent9, identificata dal tipo di sensore che genera l’aggiornamento, e creando un task schedulato periodicamente che per ogni sensore abilitato ottenga dalla lista l’ultimo aggiornamento e lo stampi nella TextView dell’inter- faccia. Nel sistema di discovery che verrà realizzato questo task creerà un MqttMessage contenente tutti gli ultimi aggiornamenti e lo invierà al broker.

AndroidManifest e layout

Diversamente dai servizi di sistema visti in precedenza, l’accesso al SensorManager non necessita di alcuna uses-permission, quindi in questo caso l’Android Manifest sarà quello generato automaticamente da Eclipse.

Per quanto riguarda il layout, mostrato in Figura 4.9 e definito nel listato A.32 , viene utilizzato un componente ListView per mostrare tutti i sensori presenti nel dispositivo, ed una TextView in cui l’ap- plicazione mostrerà periodicamente le rappresentazioni JSON dell’ultimo SensorEvent memorizzato da ogni sensore abilitato:

Figura 4.9: Android Sensor layout

Per mostrare tutti i sensori è stato inoltre realizzato un layout di riga personalizzato con cui si definisce che ogni riga della ListView sarà formata da due componenti: una CheckBox per selezionare se il tipo di sensore (il cui nome sarà stampato nella label del componente) sarà attivo o meno per l’applicazione, ed una TextViewche conterrà il modello del sensore. Il layout di ogni riga della ListView è realizzato attraverso il file sensor-row.xml definito nel listato A.33.

9Un oggetto SensorEvent contiene tutte le informazioni relative all’aggiornamento: valori misurati, timestamp, precisione e,

SensorListener

La classe SensorListener implementa l’interfaccia SensorEventListener che definisce i meto- di che verranno invocati dal SensorManager quando un sensore esegue una nuova misurazione o modifica la sua precisione. In questo esempio quando il sensore ha ottenuto un nuovo dato, questo viene aggiunto ad uno SparseArray.

SensorListenerfornisce inoltre tre metodi getter con cui ottenere gli ultimi aggiornamenti: getLastUpdate(int sensorType) restituisce l’ultimo aggiornamento di uno specifico sensore getLastUpdatesAsString() restituisce in un’unica stringa tutte le rappresentazioni JSON presenti

nello SparseArray, separate dal carattere new line (\n)

getLastUpdatesAsArray() restituisce per intero lo SparseArray di stringhe

Listato 4.11: SensorListener.java

public class SensorListener implements SensorEventListener {

private SparseArray<String> data = new SparseArray<String>();

private Gson gson = new Gson();

@Override

public void onSensorChanged(SensorEvent event) { data.put(event.sensor.getType(), gson.toJson(event)); }

@Override

public void onAccuracyChanged(Sensor sensor, int accuracy) {}

public String getLastUpdate(int sensorType){

System.out.println(""+data.get(sensorType, "No updates for " + SensorUtils.getSensorTypeAsString (sensorType)));

String ret = data.get(sensorType, "No updates for " + SensorUtils.getSensorTypeAsString( sensorType));

data.remove(sensorType);

return ret; }

public String getLastUpdatesAsString(){ String ret = "";

for(int i = 0; i < data.size(); i++) ret += data.valueAt(i)+"\n";

return ret; }

public SparseArray<String> getLastUpdatesAsArray(){

return data; }

public void remove(int type) { data.remove(type);

} }

SensorAdapter

Per poter riempire la ListView in modo programmatico è stata realizzata la classe SensorAdapter che estende ArrayAdapter<Sensor>. Questa classe ridefinisce il metodo getView, che inserirà al- l’interno della ListView una nuova riga (che avrà il layout definito da sensor_row.xml ed i suoi componenti configurati opportunamente) per ogni sensore appartenente alla lista passata al costruttore come parametro.

Il listener onCheckedChanged associato alla CheckBox aggiungerà il sensore che verrà selezionato alla lista di quelli abilitati ed invocherà un metodo sull’Activity che si occuperà di registrare un listener. Se al contrario la CheckBox verrà deselezionata, la callback rimuoverà il sensore dalla lista di quelli abilitati e richiederà all’Activity di rimuovere il listener.

La classe fornisce inoltre due metodi pubblici: il primo restituisce la lista dei sensori abilitati, mentre il secondo consente di disabilitare tutti i sensori, e vuotare la lista di quelli selezionati.

Listato 4.12: SensorAdapter.java

public class SensorAdapter extends ArrayAdapter<Sensor>{

private ArrayList<Sensor> enabledSensors = new ArrayList<Sensor>();

private SensorActivity activity;

public SensorAdapter(SensorActivity activity, int textViewResourceId, List<Sensor> list) { super(activity, textViewResourceId, list);

this.activity = activity; }

@Override

public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) getContext()

.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

convertView = inflater.inflate(R.layout.sensor_row, parent, false);

final CheckBox type = (CheckBox)convertView.findViewById(R.id.checkBox1);

final TextView name = (TextView)convertView.findViewById(R.id.textView1);

final Sensor s = (Sensor)getItem(position);

String sensorType = SensorUtils.getSensorTypeAsString(s.getType()); type.setText(sensorType);

type.setOnCheckedChangeListener(new OnCheckedChangeListener(){ @Override

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

if (isChecked){

enabledSensors.add(s);

activity.registerSensorListener(s);

Log.i("CLICK ON "+type.getText().toString(), "Enabled"); }

else{

enabledSensors.remove(s); activity.removeSensorListener(s);

Log.i("CLICK ON "+type.getText().toString(), "Disabled"); }

if(enabledSensors.isEmpty() && activity.printTaskIsRunning() ){ activity.stopPrintTask();

activity.runOnUiThread(new Runnable(){ @Override

public void run() {

((TextView)activity.findViewById(R.id.updates)).append("\n\nAll Sensors Disabled"

); } }); } } }); name.setText(s.getName()); return convertView; }

public ArrayList<Sensor> getEnabledSensors(){

return enabledSensors; }

public void removeEnabled() {

ViewGroup v = (ViewGroup)activity.findViewById(R.id.sensorList); for(int i = 0; i < v.getChildCount(); i++)

((CheckBox)v.getChildAt(i).findViewById(R.id.checkBox1)).setChecked(false); enabledSensors.clear();

} }

SensorUtils

La classe SensorUtils, definita nel listato A.34 , fornisce due metodi: il primo, ricevuto come para- metro un intero che identifica il tipo sensore come definito da Android all’interno del SensorManager, restituisce una rappresentazione dello stesso sotto forma di stringa. Il secondo metodo esegue l’opera- zione inversa: data una rappresentazione in stringa del sensore restituisce il valore intero assegnatogli dal SensorManager.

SensorActivity

In questo esempio la classe SensorActivity utilizza il metodo onCreate per ottenere tutti i ri- ferimenti agli elementi dalle UI, richiedere il SensorManager, creare un nuovo SensorAdapter da aggiungere alla ListView, ed assegnare al pulsante start/stop l’onClickListener.

Quando il pulsante viene cliccato, se non c’è alcuna azione schedulata, il listener controlla la lista dei sensori abilitati richiedendola al SensorAdapter. Se questa è vuota il metodo termina, altrimenti avvia una nuova schedulazione che manderà in esecuzione ad intervalli regolari (in questo caso ogni 5 secondi) il PrintDataTask, e cambierà il testo del pulsante. Al contrario, se c’è già una schedulazione attiva, l’uten- te avrà cliccato sul pulsante per fermarla; in questo caso verrà invocato il metodo stopPrintTask(), che dopo aver fermato l’esecuzione di PrintDataTask provvederà a modificare il testo del pulsan- te. Quest’ultimo metodo verrà invocato anche nel caso in cui tutti i sensori siano disabilitati durante la schedulazione.

La classe è definita dal listato A.35.

PrintDataTask

intervalli regolari quando l’utente abilita l’aggiornamento. Quando viene eseguito, il metodo run scorrerà la lista dei sensori abilitati, per ognuno di essi richiederà al SensorListener l’ultimo aggiornamento ricevuto, e concatenerà la stringa JSON ricevuta alla TextView. Sarebbe stato possibile ottenere lo stesso risultato con la semplice invocazione del metodo getLastUpdatesAsString evitando di scorrere la lista dei sensori attivi.

La classe è implementata dal codice mostrato nel listato A.36.