Al momento di esportare una applicazione da Eclipse mediante Android Tools, Lint ha deciso che per fare il suo lavoro dovevo effettuare la traduzione delle stringhe in tutte le lingue del mondo
Per ovviare al problema si puo' andare Window/Preference/Lint Error Checking e modificare l'errore Missing Translation da una Severity Fatal ad una Serverity Warning
mercoledì 30 aprile 2014
Hotspot e Bluetooth su Android
Ed ecco che ho sperimentato un bel bug di Android. Usando contemporaneamente l'HotSpot WiFi e Bluetooth sul Motorola Moto G con Android KitKat 4.4.2 il sistema operativo interrompe la funzione HotSpot
Questo bug e' descritto in https://code.google.com/p/android/issues/detail?id=41631 e sembra che non sia presente un Android 4.3
Fluttuazione del segnale IBeacon
Iniziando a lavorare con le libreria IBeacon di Radius si nota chiaramente che i valori di distanza sono molto variabili ed ho fatto una piccolo test con una applicazione misurando (a geometria fissa quindi con la distanza tra IBeacon e telefono fissa) i valori getAccuracy
I risultati (48 misure per circa un minuto di test) sono riportati nel grafico soprastante. Il valore medio e' stato di 0.3 (i valori delle ordinate del grafico sono moltiplicate per 100) ma con una oscillazione massima di 1.01 e minima di 0.09. In definitiva la deviazione standard e' della stessa dimensione del valore medio
Cio' crea ovviamente problemi nella gestione della prossimita' al beacon
Il codice dell'applicazione puo' essere scaricato a questo indirizzo
lunedì 28 aprile 2014
April Beacon
Il dispositivo che mi sono trovato a testare e' un AprilBeacon 201 ai aprbrother.com basato sul modulo CC2540 BLE
L'alimentazione e' data da una pila CR2450 (da comprare a parte). Il modello e' completamente configurabile ed oltre a poter cambiare l'UUID (di default e' quello di test di Apple) e' possibile anche variare il Major e Minor Number la potenza di trasmissione
Usando la app AprilBeacon su Google Play si possono visualizzare i parametri di esercizio (per effettuare tale operazione si deve inserire il pin che e' 19566)
dalla applicazione si puo' vedere il livello della batteria, la potenza del segnale e le informazioni sul firmware
L'alimentazione e' data da una pila CR2450 (da comprare a parte). Il modello e' completamente configurabile ed oltre a poter cambiare l'UUID (di default e' quello di test di Apple) e' possibile anche variare il Major e Minor Number la potenza di trasmissione
Usando la app AprilBeacon su Google Play si possono visualizzare i parametri di esercizio (per effettuare tale operazione si deve inserire il pin che e' 19566)
dalla applicazione si puo' vedere il livello della batteria, la potenza del segnale e le informazioni sul firmware
Al contrario dell'applicazione IOS (che si scarica da qui) non e' possibile modificare l'UUID. Il customer care della ditta dice che tale funzionalita' sara' disponibile tra circa un mese
IBeacon con Radius Library
Per provare IBeacon su Android vale decisamente la pena di scaricare la libreria di Radius Networks http://developer.radiusnetworks.com/ibeacon/android/pro/download.html (esiste una versione open ed una pro a pagamento)
Per iniziare ad usare la libreria AndroidBeaconLibrary bisogna importarla in Eclipse come un normale progetto Android mediante File/Import/ExistingAndroidCode
A questo punto si crea un nuovo progetto Applicazione Android in cui si deve impostare la versione di Android ad almeno la nr 18 (Android 4.3 JellyBean) e si aggiungono i seguenti permessi al Manifest
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
dopo al file project.properties si aggiunge
manifestmerger.enabled=true
a questo punto e' meglio fare fare Project/Clean, Project/Refresh
e si possono usare gli esempi riportati a questa pagina
http://developer.radiusnetworks.com/ibeacon/android/samples.html
(vedi sotto)
Se non si ha un iBeacon reale si puo' simulare mediante la macchina virtuale Virtualbox messa a disposizione sempre da Radius
Io ho utilizzato un dongle bluetooth 4 Broadcom BCM920702 che deve collegato alla macchina virtuale tra i dispositivi USB. Al termine del boot compare qualcosa di simile
Premedo un tasto si accede alla shell
Il servizio IBeacon si avvia e si stoppa con i comandi "start" e "stop". Nella directory di root si trova il file di configurazione ibeacon.conf che permette di modificare i parametri.
Attenzione : di default la macchina virtuale ha un UUID di E2C56DB5-DFFB-48D2-B060-D0F5A71096E0 che e' quello di test come definito da Apple
Di seguito l'esempio Ranging. Ho effettuato una modifica (indicata in giallo) perche' cosi' come proposto da Radius il programma identifica solo il primo iBeacon che riesce a vedere mentre iterando la Collection si possono ottenere le informazioni di tutti gli iBeacon visibili
---------------------------------------------------
package com.example.ibea;
import java.util.Collection;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.radiusnetworks.ibeacon.IBeacon;
import com.radiusnetworks.ibeacon.IBeaconConsumer;
import com.radiusnetworks.ibeacon.IBeaconManager;
import com.radiusnetworks.ibeacon.Region;
import com.radiusnetworks.ibeacon.RangeNotifier;
public class MainActivity extends Activity {
private IBeaconManager iBeaconManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
iBeaconManager.bind((IBeaconConsumer) this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
iBeaconManager.unBind((IBeaconConsumer) this);
}
public void onIBeaconServiceConnect() {
iBeaconManager.setRangeNotifier(new RangeNotifier() {
@Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons,Region arg1) {
if (iBeacons.size() > 0) {
for (IBeacon iBeacon : iBeacons) {
Log.d(TAG,iBeacon.getProximityUuid()+" - "+iBeacon.getAccuracy());
}
}
}
});
try {
iBeaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
} catch (RemoteException e) { }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
Attenzione: non si tratta del medesimo dispositivo listato due volte ma due IBeacon che condividono lo stesso UUID di prova |
A questo punto si crea un nuovo progetto Applicazione Android in cui si deve impostare la versione di Android ad almeno la nr 18 (Android 4.3 JellyBean) e si aggiungono i seguenti permessi al Manifest
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
dopo al file project.properties si aggiunge
manifestmerger.enabled=true
a questo punto e' meglio fare fare Project/Clean, Project/Refresh
e si possono usare gli esempi riportati a questa pagina
http://developer.radiusnetworks.com/ibeacon/android/samples.html
(vedi sotto)
Se non si ha un iBeacon reale si puo' simulare mediante la macchina virtuale Virtualbox messa a disposizione sempre da Radius
Io ho utilizzato un dongle bluetooth 4 Broadcom BCM920702 che deve collegato alla macchina virtuale tra i dispositivi USB. Al termine del boot compare qualcosa di simile
Premedo un tasto si accede alla shell
Il servizio IBeacon si avvia e si stoppa con i comandi "start" e "stop". Nella directory di root si trova il file di configurazione ibeacon.conf che permette di modificare i parametri.
Attenzione : di default la macchina virtuale ha un UUID di E2C56DB5-DFFB-48D2-B060-D0F5A71096E0 che e' quello di test come definito da Apple
Di seguito l'esempio Ranging. Ho effettuato una modifica (indicata in giallo) perche' cosi' come proposto da Radius il programma identifica solo il primo iBeacon che riesce a vedere mentre iterando la Collection si possono ottenere le informazioni di tutti gli iBeacon visibili
---------------------------------------------------
package com.example.ibea;
import java.util.Collection;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.radiusnetworks.ibeacon.IBeacon;
import com.radiusnetworks.ibeacon.IBeaconConsumer;
import com.radiusnetworks.ibeacon.IBeaconManager;
import com.radiusnetworks.ibeacon.Region;
import com.radiusnetworks.ibeacon.RangeNotifier;
public class MainActivity extends Activity {
private IBeaconManager iBeaconManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
iBeaconManager.bind((IBeaconConsumer) this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
iBeaconManager.unBind((IBeaconConsumer) this);
}
public void onIBeaconServiceConnect() {
iBeaconManager.setRangeNotifier(new RangeNotifier() {
@Override
public void didRangeBeaconsInRegion(Collection<IBeacon> iBeacons,Region arg1) {
if (iBeacons.size() > 0) {
for (IBeacon iBeacon : iBeacons) {
Log.d(TAG,iBeacon.getProximityUuid()+" - "+iBeacon.getAccuracy());
}
}
}
});
try {
iBeaconManager.startRangingBeaconsInRegion(new Region("myRangingUniqueId", null, null, null));
} catch (RemoteException e) { }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
iBeacon con Linux, Yun e Raspberry
Trasformare un dispositivo basato su Linux in un IBeacon e' piuttosto semplice
Per prima cosa si deve avere un dongle USB Bluetooth LTE 4.0
La preparazione e' differente per i dispositivi ma una volta che sono installate le librerie BT i comandi successivi sono comunu
Preparazione Arduino YUN
Per trasformare Arduino Yun in un IBeacon si possono seguire le istruzioni a questo link
Una volta collegati in shell SSH con Yun si digitano i seguenti comandi per installare lo stack bluetooth
opkg update
opkg install kmod-bluetooth
(Yun non ha abilitato SFTP e quindi e' meglio scaricare il file direttamente su Yun)
wget http://fibasile.github.io/images/arduino-yun-ibeacon/bluez_5.13-1_ar71xx.ipk
opkg install bluez_5.13-1_ar71xx.ipk
Preparazione Linux (Ubuntu 12.04) e Raspberry PI
In questo caso si deve aggiornare la libreria BT si default scaricando i sorgenti
Si scarica quindi la libreria da qui (o versione successiva)
si compila con il classico
configure --disable-systemd
make
make install
soddisfacendo le dipendenze. Io ho dovuto eseguire
apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev
Parte Comune
hciconfig hci0 up
hciconfig hci0 leadv
hciconfig hci0 noscan
hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A
FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C8 00
dove la sintassi dell'ultimo comando e' la seguente
hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 <UUID> <MAJOR> <MINOR> <POWER> 00
a questo punto il beacon e' rilevabile
giovedì 24 aprile 2014
Listview cliccabile su Android
Un esempio minimale di Listview cliccabile su Android che funziona sui Fragment
--------------------------------------------------------------
package com.example.lista;
import java.util.ArrayList;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
public static ListView lista;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
lista = (ListView) rootView.findViewById(R.id.listView1);
String[] cognome = new String[] { "Innocenti", "Scialla", "Parauda" };
final ArrayList <String> listp = new ArrayList<String>();
for (int i = 0; i < cognome.length; ++i) {
listp.add(cognome[i]);
}
ArrayAdapter <String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, listp);
lista.setAdapter(adapter);
lista.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getActivity(),((TextView) view).getText(), Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
}
}
--------------------------------------------------------------
package com.example.lista;
import java.util.ArrayList;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
public static ListView lista;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
lista = (ListView) rootView.findViewById(R.id.listView1);
String[] cognome = new String[] { "Innocenti", "Scialla", "Parauda" };
final ArrayList <String> listp = new ArrayList<String>();
for (int i = 0; i < cognome.length; ++i) {
listp.add(cognome[i]);
}
ArrayAdapter <String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, listp);
lista.setAdapter(adapter);
lista.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getActivity(),((TextView) view).getText(), Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
}
}
mercoledì 23 aprile 2014
JSON da Mysql ad Android
Per poter effettuare lo scambio dati (unidirezionale) tra un server ed un client mobile Android puo' essere utile impiegare JSon
Per prima cosa si deve configurare il lato server installando Apache,Php e le sue estensioni (in particolare php5-json) e mysql
sudo apt-get install apache2
sudo apt-get install php5
sudo apt-get install libapache2-mod-php5
sudo apt-get install php5-json
sudo /etc/init.d/apache2 restart
sudo apt-get install mysql-server phpmyadmin
A questo punto si crea la base dati. Nell'esempio e' stato creato un database "test" con una sola tabella "test" con una struttura di questo tipo
----------------------------------------------------
-- phpMyAdmin SQL Dump
-- version 3.4.10.1deb1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Apr 23, 2014 at 04:15 PM
-- Server version: 5.5.35
-- PHP Version: 5.3.10-1ubuntu3.11
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `test`
--
-- --------------------------------------------------------
--
-- Table structure for table `test`
--
CREATE TABLE IF NOT EXISTS `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nome` varchar(50) NOT NULL,
`cognome` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `test`
--
INSERT INTO `test` (`id`, `nome`, `cognome`) VALUES
(1, 'Luca', 'Innocenti'),
(2, 'Alessio', 'Parauda');
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
----------------------------------------------------
----------------------------------------------------
package com.luca.json;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
String result = readJson();
JSONArray jArray = null;
try {
jArray = new JSONArray(result);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject json_data=null;
for(int i=0;i<jArray.length();i++){
try {
json_data = jArray.getJSONObject(i);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
String nr = Integer.toString(json_data.getInt("id"));
String nome = json_data.getString("nome");
String cognome = json_data.getString("cognome");
Log.d("JSON",nr+","+nome+","+cognome);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
thread.start();
}
private String readJson() {
String line = null;
StringBuilder builder = new StringBuilder();
HttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://192.168.0.100/json.php");
try {
HttpResponse response = client.execute(httpGet);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} else {
Log.d("JSON", "Failed to download file");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//Log.d("JSON",line);
return builder.toString();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
Per prima cosa si deve configurare il lato server installando Apache,Php e le sue estensioni (in particolare php5-json) e mysql
sudo apt-get install apache2
sudo apt-get install php5
sudo apt-get install libapache2-mod-php5
sudo apt-get install php5-json
sudo /etc/init.d/apache2 restart
sudo apt-get install mysql-server phpmyadmin
A questo punto si crea la base dati. Nell'esempio e' stato creato un database "test" con una sola tabella "test" con una struttura di questo tipo
----------------------------------------------------
-- phpMyAdmin SQL Dump
-- version 3.4.10.1deb1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Generation Time: Apr 23, 2014 at 04:15 PM
-- Server version: 5.5.35
-- PHP Version: 5.3.10-1ubuntu3.11
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `test`
--
-- --------------------------------------------------------
--
-- Table structure for table `test`
--
CREATE TABLE IF NOT EXISTS `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nome` varchar(50) NOT NULL,
`cognome` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `test`
--
INSERT INTO `test` (`id`, `nome`, `cognome`) VALUES
(1, 'Luca', 'Innocenti'),
(2, 'Alessio', 'Parauda');
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
----------------------------------------------------
per interrogare il db sul lato server e' stato inserito il seguente semplice script Python che effettua una query e crea un oggetto JSon con i dati risultanti dalla query
----------------------------------------------------
<?php
$mysql_db_hostname = "localhost";
$mysql_db_user = "xxxxx";
$mysql_db_password = "xxxxxx";
$mysql_db_database = "test";
$con = @mysqli_connect($mysql_db_hostname, $mysql_db_user, $mysql_db_password,$mysql_db_database);
if (!$con) {
echo('Could not connect to MySQL: ' . mysqli_connect_error());
}
$var = array();
$sql = "SELECT * FROM test";
$result = mysqli_query($con, $sql);
while($obj = mysqli_fetch_object($result)) {
$var[] = $obj;
}
echo json_encode($var);
?>
----------------------------------------------------
si puo' testare l'output chiamando la pagina da browser
L'applicazione Android di seguito riportata apre una connessione Http verso la pagina php precedentemente predisposta ed effettua il parsing dell'oggetto JSon
Ovviamente deve essere impostato nel file Manifest il permesso per l'uso di Internet
package com.luca.json;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
String result = readJson();
JSONArray jArray = null;
try {
jArray = new JSONArray(result);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject json_data=null;
for(int i=0;i<jArray.length();i++){
try {
json_data = jArray.getJSONObject(i);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
String nr = Integer.toString(json_data.getInt("id"));
String nome = json_data.getString("nome");
String cognome = json_data.getString("cognome");
Log.d("JSON",nr+","+nome+","+cognome);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
thread.start();
}
private String readJson() {
String line = null;
StringBuilder builder = new StringBuilder();
HttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("http://192.168.0.100/json.php");
try {
HttpResponse response = client.execute(httpGet);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
InputStream content = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} else {
Log.d("JSON", "Failed to download file");
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//Log.d("JSON",line);
return builder.toString();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
return rootView;
}
}
}
Accuratezza del sensore di distanza di Kinect (3)
Sempre per cercare di capire quale sono i limiti del Kinect ho provato a riprendere un muro il piu' possibile in modo perpendicolare
Il risultato e' chiaramente visibile nell'immagine. Anche si tratta di uno zoom piuttosto estremo si vede che l'immagine e' rumorosa con valori anche di +/-2,3 mm millimetri rispetto al valore medio
Il risultato e' chiaramente visibile nell'immagine. Anche si tratta di uno zoom piuttosto estremo si vede che l'immagine e' rumorosa con valori anche di +/-2,3 mm millimetri rispetto al valore medio
martedì 22 aprile 2014
Prova Kinect sulle Mura Etrusche di Fiesole
Con lo stesso sistema del precedente post ho provato a scansionare le Mura Etrusche di Fiesole
Link ai dati
Di seguito i risultati (decisamente incoraggianti) con il confronto tra l'immagine RGB e la Mesh dei dati di profondita' (elaborati con CloudCompare)
Link ai dati
Kinect per geologia
Attrezzatura da campagna |
Visto che il Kinect ha necessita' di una alimentazione esterna ho preso un inverter della Belkin da 130 W che ha svolto egregiamente il proprio lavoro (il portatile era alimentato mediante la propria batteria)
Le misure sono state effettuate in condizioni di pioggia battente ed affioramenti bagnati (tanto per rendere le condizioni di misura estreme). E' stato verificato che oltre il metro di distanza i risultati erano scarsi probabilmente perche' la roccia bagnata assorbe molto di piu' il laser che in condizioni asciutte e quindi non permette di calcolare la distanza
Foto ad alta risoluzione da cellulare del primo affioramento |
Confronto tra immagine RGB e Mesh |
E' stato effettuato un altro rilievo in condizioni ancora peggiori perche' la superficie di affioramento non era ortogonale ma molto inclinata rispetto alla verticale
Immagine RGB da Kinect |
Elaborazione Mesh della CloudPoint da Kinect |
Qui si possono scaricare i dati originali (per un problema di acquisizione le immagini RGB sono specchiate lungo l'asse maggiore, nel post le immagini sono state corrette)
PointCloud e Mesh con CloudCompare e Meshlab
Una volta acquisita una nuvola di punti con Kinect e' necessario processarla per ripulire i dati anomali e passare da una nuvola di punti (PointCloud) ad una superficie (Mesh)
Sono disponibili un paio di software OpenSource
Il primo e' CloudCompare (disponibile per vari OS)
Per la versione Ubuntu si installa mediante PPA con la seguente procedura
sudo add-apt-repository ppa:romain-janvier/cloudcompare
apt-get update
Sono disponibili un paio di software OpenSource
Il primo e' CloudCompare (disponibile per vari OS)
Ricostruzione della superficie con CloudCompare |
La superficie reale scansionata |
Per la versione Ubuntu si installa mediante PPA con la seguente procedura
sudo add-apt-repository ppa:romain-janvier/cloudcompare
apt-get update
apt-get cloudcompare
vengono installati due software CloudCompare (il vero e proprio software di elaborazione ed analisi.attenzione alle maiuscole!!) e ccViewer (il solo visualizzatore)
La procedura per generare una mesh da una nuvola di punti in CloudCompare prevede
dal DBTree selezionare la Cloud di interesse
Edit/Normal/Compute
Edit/Mesh/Delanauy 2D
Edit/Color
eventualmente si puo' dare smoothing con
Edit/Mesh/Smooth (Laplacian)
L'alternativa e' Meshlab che si trova direttamente nei repository
apt-get install meshlab
anche se in una versione piu' vecchia della corrispondente versione Windows
Con Meshlab si ha a disposizione un comodo strumento di misura ma non sono mai riuscito a portare a termine la conversione di una PointCloud in una Mesh perche' il programma crasha (sembra che il numero dei punti sia troppo elevato)
In ogni caso la procedura dovrebbe essere
dal menu Filters
Sampling/Possoin Disk sampling /check Base Mesh Subsampling
Normals,Curvatures/Compute Normals for Point Sets
Point Set/Poisson Reconstruction
Cleaning and Repairing/Remove isolated Pieces (wrt face num)
vengono installati due software CloudCompare (il vero e proprio software di elaborazione ed analisi.attenzione alle maiuscole!!) e ccViewer (il solo visualizzatore)
La procedura per generare una mesh da una nuvola di punti in CloudCompare prevede
dal DBTree selezionare la Cloud di interesse
Edit/Normal/Compute
Edit/Mesh/Delanauy 2D
Edit/Color
eventualmente si puo' dare smoothing con
Edit/Mesh/Smooth (Laplacian)
L'alternativa e' Meshlab che si trova direttamente nei repository
apt-get install meshlab
anche se in una versione piu' vecchia della corrispondente versione Windows
La stessa nuvola dei punti vista prima ma elaborata in Meshlav |
In ogni caso la procedura dovrebbe essere
dal menu Filters
Sampling/Possoin Disk sampling /check Base Mesh Subsampling
Normals,Curvatures/Compute Normals for Point Sets
Point Set/Poisson Reconstruction
Cleaning and Repairing/Remove isolated Pieces (wrt face num)
lunedì 21 aprile 2014
Chrome Remote Desktop
Contrariamente a quanto prevede il nome Chrome Remote non e' una estensione in senso stretto del browser. Pur installando una componente dal WebStore di fatto in seguito viene scaricato un eseguibile indipendente dal browser che effettua tutto il lavoro di server di remote desktop
Il componente di Google Remote Desktop nelle Applicazioni di Chrome |
Definizione dei pin di sicurezza |
Installazione del server |
Giusto per confermare come Chrome Remote Desktop sia distaccata dal browser Chrome e' possibile accedere alla macchina remota anche se sulla macchina remota non e' avviato il browser
Rispetto ad altri esempi di software di desktop remoto, Chrome Remote Desktop ha un fantastico client perfettamente integrato in Android
In alto a destra l'icona per aprire la tastiera remote |
Programma completo per Kinect
Utilizzando tutte le esperienze precedenti (1,2) ho scritto questo programmino che salva in un colpo solo tutti i parametri di Kinect
lo script deve essere lanciato con una stringa argomento che diventa il nome del progetto.
per esempio
./acquisizione test
genera poi i file
test_angolo.txt
test_rgb.jpg
test_cloud.asc
test_cloud.txt
nel file _angolo.txt sono salvati gli angolo di pitch, roll, la distanza al centro dell'immagine ed il numero di punti della nuvola che risultano corretti (non tutti i pixel di una ascquisizione con il laser risultano corretti, valori attorno al 70% sono gia' ottimali
nel file _rgb.rgb viene salvata una fotografia della scansione. Attenzione: in alcuni casi ci sono problemi di sincronia per cui questa immagine puo' risultare tagliata o mescolata al frame precedente
nel file _cloud.asc sono riportati i valori in x,y,z in millimetri della scansione gia' pronti per essere inclusi in Meshlab o Cloudcompare
il file _cloud.txt e' l'acquisizione della nuvola dei punti pura senza elaborazione e serve nel caso ci siano problemi al punto precedente
il ritardo iniziale serve solo a dare il tempo all'operatore di mettersi in posizione con il kinect
--------------------------------------
#!/usr/bin/python
import usb.core
import usb.util
import sys
import time
import math
from openni import *
from PIL import Image
import numpy as np
import pickle
print "10 secondi alla misura"
time.sleep(5)
print "5 secondi alla misura"
time.sleep(5)
print "Inizio misura"
stringa = sys.argv[1]
print "Progetto : "+stringa
dev = usb.core.find(idVendor=0x045e, idProduct=0x02B0)
if dev is None:
raise ValueError('Device not found')
for cfg in dev:
sys.stdout.write("Configuration #"+str(cfg.bConfigurationValue) + '\n')
for intf in cfg:
sys.stdout.write('\tInterface #' + \
str(intf.bInterfaceNumber) + \
'\t, Alternate setting ' + \
str(intf.bAlternateSetting) + \
'\n')
sys.stdout.write("\tEndpoints:\n")
for ep in intf:
sys.stdout.write('\t\t' + \
str(ep.bEndpointAddress) + \
'\n')
dev.set_configuration()
ret = dev.ctrl_transfer(0xC0, 0x10, 0x0, 0x0, 1)
ret = dev.ctrl_transfer(0x40, 0x6, 0x1, 0x0, [])
#calcola l'angolo
ret = dev.ctrl_transfer(0xC0, 0x32, 0x0, 0x0, 10)
x = (ret[2] << 8) | ret[3]
x = (x + 2**15) % 2**16 - 2**15 # convert to signed 16b
y = (ret[4] << 8) | ret[5]
y = (y + 2**15) % 2**16 - 2**15 # convert to signed 16b
z = (ret[6] << 8) | ret[7]
z = (z + 2**15) % 2**16 - 2**15 # convert to signed 16b
pitch = math.atan2(y,x)*(180/3.1415926)
roll = math.atan2(y,math.sqrt((x*x)+(z*z)))*(180/3.1415926)
if (z <0):
roll = 90+(90-roll)
#immagine rgb e profondita'
ctx = Context()
ctx.init()
depth = DepthGenerator()
rgb = ImageGenerator()
depth.create(ctx)
rgb.create(ctx)
depth.set_resolution_preset(RES_VGA)
depth.fps = 30
rgb.set_resolution_preset(RES_VGA)
rgb.fps = 30
ctx.start_generating_all()
ctx.wait_one_update_all(rgb)
im = Image.fromstring('RGB',(640,480),rgb.get_raw_image_map())
im.save(stringa+"_rgb.jpg")
print "RGB salvata"
#ctx.wait_one_update_all(depth)
#de = Image.fromstring('L',(640,480),depth.get_raw_depth_map_8())
#de.save(stringa+"_depth.jpg")
#print "Depth salvata"
time.sleep(0.5)
#mappa_punti
ctx.wait_one_update_all(depth)
depthMap = depth.map
depthMap2 = np.array(depthMap)
f = open(stringa+"_cloud.txt","w")
pickle.dump(depthMap2,f)
f.close()
f = open(stringa+"_cloud.txt")
data = pickle.load(f)
f.close()
g = open(stringa+"_cloud.asc","w")
t = 0
d = 0
alfa = -0.5105088 # angolo lungo x
delta_a = 0.00159534 # delta alfa
beta = 0.397935 # angolo lungo beta
delta_b = 0.001658052 # delta beta
dis_c = data[153600] # distanza del punto centrale 320x240
print "Distanza media "+str(dis_c) + " mm"
for y in range(0,480):
alfa = -0.5105088
for x in range (0,640):
if (data[t] !=0):
g.write(str(dis_c*math.tan(alfa))+","+str(dis_c*math.tan(beta))+","+str(data[t])+"\n")
d = d + 1
t=t+1
alfa = alfa + delta_a
beta = beta - delta_b
g.close()
perc = (d/307200.0)*100
print "Nuvola dei punti salvati"
print "Punti validi : " + str(round(perc,1)) + "%"
out = open(stringa + "_angolo.txt","w")
out.write("Pitch: "+str(round(pitch,1))+", Roll : "+str(round(roll,1))+", Distanza : "+str(dis_c)+" mm, Punti validi :"+str(round(perc,1))+"% \n")
out.close()
print "Angoli salvati"
lo script deve essere lanciato con una stringa argomento che diventa il nome del progetto.
per esempio
./acquisizione test
genera poi i file
test_angolo.txt
test_rgb.jpg
test_cloud.asc
test_cloud.txt
nel file _angolo.txt sono salvati gli angolo di pitch, roll, la distanza al centro dell'immagine ed il numero di punti della nuvola che risultano corretti (non tutti i pixel di una ascquisizione con il laser risultano corretti, valori attorno al 70% sono gia' ottimali
nel file _rgb.rgb viene salvata una fotografia della scansione. Attenzione: in alcuni casi ci sono problemi di sincronia per cui questa immagine puo' risultare tagliata o mescolata al frame precedente
nel file _cloud.asc sono riportati i valori in x,y,z in millimetri della scansione gia' pronti per essere inclusi in Meshlab o Cloudcompare
il file _cloud.txt e' l'acquisizione della nuvola dei punti pura senza elaborazione e serve nel caso ci siano problemi al punto precedente
il ritardo iniziale serve solo a dare il tempo all'operatore di mettersi in posizione con il kinect
--------------------------------------
#!/usr/bin/python
import usb.core
import usb.util
import sys
import time
import math
from openni import *
from PIL import Image
import numpy as np
import pickle
print "10 secondi alla misura"
time.sleep(5)
print "5 secondi alla misura"
time.sleep(5)
print "Inizio misura"
stringa = sys.argv[1]
print "Progetto : "+stringa
dev = usb.core.find(idVendor=0x045e, idProduct=0x02B0)
if dev is None:
raise ValueError('Device not found')
for cfg in dev:
sys.stdout.write("Configuration #"+str(cfg.bConfigurationValue) + '\n')
for intf in cfg:
sys.stdout.write('\tInterface #' + \
str(intf.bInterfaceNumber) + \
'\t, Alternate setting ' + \
str(intf.bAlternateSetting) + \
'\n')
sys.stdout.write("\tEndpoints:\n")
for ep in intf:
sys.stdout.write('\t\t' + \
str(ep.bEndpointAddress) + \
'\n')
dev.set_configuration()
ret = dev.ctrl_transfer(0xC0, 0x10, 0x0, 0x0, 1)
ret = dev.ctrl_transfer(0x40, 0x6, 0x1, 0x0, [])
#calcola l'angolo
ret = dev.ctrl_transfer(0xC0, 0x32, 0x0, 0x0, 10)
x = (ret[2] << 8) | ret[3]
x = (x + 2**15) % 2**16 - 2**15 # convert to signed 16b
y = (ret[4] << 8) | ret[5]
y = (y + 2**15) % 2**16 - 2**15 # convert to signed 16b
z = (ret[6] << 8) | ret[7]
z = (z + 2**15) % 2**16 - 2**15 # convert to signed 16b
pitch = math.atan2(y,x)*(180/3.1415926)
roll = math.atan2(y,math.sqrt((x*x)+(z*z)))*(180/3.1415926)
if (z <0):
roll = 90+(90-roll)
#immagine rgb e profondita'
ctx = Context()
ctx.init()
depth = DepthGenerator()
rgb = ImageGenerator()
depth.create(ctx)
rgb.create(ctx)
depth.set_resolution_preset(RES_VGA)
depth.fps = 30
rgb.set_resolution_preset(RES_VGA)
rgb.fps = 30
ctx.start_generating_all()
ctx.wait_one_update_all(rgb)
im = Image.fromstring('RGB',(640,480),rgb.get_raw_image_map())
im.save(stringa+"_rgb.jpg")
print "RGB salvata"
#ctx.wait_one_update_all(depth)
#de = Image.fromstring('L',(640,480),depth.get_raw_depth_map_8())
#de.save(stringa+"_depth.jpg")
#print "Depth salvata"
time.sleep(0.5)
#mappa_punti
ctx.wait_one_update_all(depth)
depthMap = depth.map
depthMap2 = np.array(depthMap)
f = open(stringa+"_cloud.txt","w")
pickle.dump(depthMap2,f)
f.close()
f = open(stringa+"_cloud.txt")
data = pickle.load(f)
f.close()
g = open(stringa+"_cloud.asc","w")
t = 0
d = 0
alfa = -0.5105088 # angolo lungo x
delta_a = 0.00159534 # delta alfa
beta = 0.397935 # angolo lungo beta
delta_b = 0.001658052 # delta beta
dis_c = data[153600] # distanza del punto centrale 320x240
print "Distanza media "+str(dis_c) + " mm"
for y in range(0,480):
alfa = -0.5105088
for x in range (0,640):
if (data[t] !=0):
g.write(str(dis_c*math.tan(alfa))+","+str(dis_c*math.tan(beta))+","+str(data[t])+"\n")
d = d + 1
t=t+1
alfa = alfa + delta_a
beta = beta - delta_b
g.close()
perc = (d/307200.0)*100
print "Nuvola dei punti salvati"
print "Punti validi : " + str(round(perc,1)) + "%"
out = open(stringa + "_angolo.txt","w")
out.write("Pitch: "+str(round(pitch,1))+", Roll : "+str(round(roll,1))+", Distanza : "+str(dis_c)+" mm, Punti validi :"+str(round(perc,1))+"% \n")
out.close()
print "Angoli salvati"
Iscriviti a:
Post (Atom)
Debugger integrato ESP32S3
Aggiornamento In realta' il Jtag USB funziona anche sui moduli cinesi Il problema risiede nell'ID USB della porta Jtag. Nel modulo...
-
In questo post viene indicato come creare uno scatterplot dinamico basato da dati ripresi da un file csv (nel dettaglio il file csv e' c...
-
La scheda ESP32-2432S028R monta un Esp Dev Module con uno schermo TFT a driver ILI9341 di 320x240 pixels 16 bit colore.Il sito di riferiment...
-
Questo post e' a seguito di quanto gia' visto nella precedente prova Lo scopo e' sempre il solito: creare un sistema che permet...