5. TP 2 - Android tablet ile Arduino'ları kontrol etme
Şimdi bir tableti kullanarak bir Arduino kartını nasıl kontrol edeceğimizi öğreneceğiz. İzlenecek örnek, dersin [client-android-skel] projesidir (bkz. 2. paragraf).
5.1. Projenin mimarisi
Projenin tamamı aşağıdaki mimariye sahip olacaktır:
![]() |
- [1] bloğu, web sunucusu / jSON ve Arduino'lar size verilecektir;
- [2] bloğunu ve web sunucusu / jSON ile iletişim kurmak üzere Android tabletin programlamasını kendiniz yapmanız gerekecektir.
5.2. Donanım
Aşağıdaki bileşenler emrinizdedir:
- Ethernet uzantısı, bir LED ve bir sıcaklık sensörü bulunan bir Arduino;
- başka bir öğrenciyle paylaşacağınız bir miniHub;
- Arduino'ya güç sağlamak için bir USB kablosu;
- Arduino ve PC'i aynı özel ağa bağlamak için iki adet ağ kablosu;
- bir Android tablet;
5.2.1. Arduino
![]() |
Farklı bileşenleri birbirine bağlamak için aşağıdaki adımları izleyin:
- PC cihazınızdaki ağ kablosunu çıkarın;
- PC cihazınızı ve Arduino'yu bir ağ kablosuyla birbirine bağlayın;
- Elinizdeki Arduino önceden programlanmış olacaktır. IP cihazının adresi [192.168.2.2] olacaktır. PC cihazınızın Arduino'yu görebilmesi için, [192.168.2] ağında ona IP adresi vermeniz gerekir. Arduino'lar, IP ve [192.168.2.1] adreslerine sahip bir PC ile iletişim kuracak şekilde programlanmıştır. İşlem şu şekildedir:
[Panneau de configuration\Réseau et Internet\Centre Réseau et partage] adresine gidin:
![]() |
- [1] adresindeyken, [réseau local] bağlantısına tıklayın;
- [2] sayfasında, yerel ağdaki [Propriétés] düğmesine tıklayın;
![]() | ![]() |
- [3]'te, [réseau local] kartının [IPv4] özelliklerine tıklayın;
- [4]'te, bu karta IP [192.168.2.1] adresini ve [255.255.255.0] alt ağ maskesini atayın;
- [5]'te, sihirbazdan çıkmak için [OK]'e gerekli sayıda tıklayın.
5.2.2. Tablet
- Wi-Fi şifrenizi kullanarak cihazınızı size belirtilecek Wi-Fi ağına bağlayın. Aynısını tabletiniz için de yapın;
- bir pencerede [ipconfig] yazarak IP cihazınızın Wi-Fi adresini kontrol edin. [192.168.x.y] gibi bir adres göreceksiniz;
dos>ipconfig
Configuration IP de Windows
Carte réseau sans fil Wi-Fi :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::39aa:47f6:7537:f8e1%2
Adresse IPv4. . . . . . . . . . . . . .: 192.168.1.25
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . : 192.168.1.1
- Tabletinizin IP Wi-Fi adresini kontrol edin. Nasıl yapılacağını bilmiyorsanız, danışmanınıza sorun. [192.168.x.z] gibi bir adres bulacaksınız;
- etkinse PC cihazınızın güvenlik duvarını devre dışı bırakın [Panneau de configuration\Système et sécurité\Pare-feu Windows];
- Bir DOS penceresinde, PC komutunu yazarak [ping 192.168.x.z] ile tabletinizin [192.168.x.z] adresi arasında iletişim kurulabildiğini kontrol edin; burada IP, tabletinizin adresidir. Tablet şu yanıtı vermelidir:
dos>ping 192.168.1.26
Envoi d'une requête 'Ping' 192.168.1.26 avec 32 octets de données :
Réponse de 192.168.1.26 : octets=32 temps=102 ms TTL=64
Réponse de 192.168.1.26 : octets=32 temps=134 ms TTL=64
Réponse de 192.168.1.26 : octets=32 temps=168 ms TTL=64
Réponse de 192.168.1.26 : octets=32 temps=208 ms TTL=64
Statistiques Ping pour 192.168.1.26:
Paquets : envoyés = 4, reçus = 4, perdus = 0 (perte 0%),
Durée approximative des boucles en millisecondes :
Minimum = 102ms, Maximum = 208ms, Moyenne = 153ms
Sisteminizin ağ yapılandırması artık hazırdır.
5.2.3. [Genymotion] emülatörü
[Genymotion] emülatörü (bkz. paragraf 6.9), tabletin yerine kullanılabilir. Neredeyse aynı hızda çalışır ve Wi-Fi ağına ihtiyaç duymaz. Bu yöntemin kullanılması tavsiye edilir. Uygulamanızın son kontrolü için tableti kullanabilirsiniz.
5.3. Arduino Programlama
Burada Arduino'lar için C kodu yazmaya odaklanacağız:
![]() |
Okunması gerekenler
- Arduino geliştirme ortamının kurulumu (bkz. paragraf 6.1);
- jSON kütüphanelerinin kullanımı (Ekler, paragraf 6.6);
- Arduino'da, bir sunucu örneğini (örneğin web sunucusu) ve bir istemci örneğini (örneğin Telnet istemcisi) test etme;
- 6.1. paragrafındaki Arduino programlama ortamına ilişkin ekler.
![]() |
Arduino, donanıma bağlı bir dizi pinden oluşur. Bu pimler giriş veya çıkışlardır. Değerleri ikili veya analogdur. Arduino'yu kontrol etmek için iki temel işlem vardır:
- numarasıyla belirtilen bir pime ikili / analog bir değer yazmak;
- numarasıyla belirtilen bir pinden ikili / analog bir değer okumak;
Bu iki temel işleme, üçüncü bir işlem daha ekleyeceğiz:
- bir LED'i belirli bir süre boyunca ve belirli bir frekansta yanıp sönmesini sağlamak. Bu işlem, önceki iki temel işlemi tekrar tekrar çağırarak gerçekleştirilebilir. Ancak testlerde göreceğiz ki, [DAO] katmanı ile Arduino arasındaki veri alışverişi saniye mertebesindedir. Bu durumda, örneğin her 100 milisaniyede bir bir LED'i yanıp sönmeye zorlamak mümkün değildir. Bu nedenle, bu yanıp sönme işlevini Arduino'nun kendisine entegre edeceğiz.
Arduino'nun çalışma şekli şu şekilde olacaktır:
- [DAO] katmanı ile bir Arduino arasındaki iletişim, TCP-IP ağı üzerinden jSON (JavaScript Nesne Notasyonu) formatında metin satırlarının alışverişi yoluyla gerçekleşir;
- başlangıçta Arduino, [DAO] katmanında bulunan bir kayıt sunucusunun 100 numaralı bağlantı noktasına bağlanır. Sunucuya tek bir metin satırı gönderir:
Bu, bağlanan Arduino'yu tanımlayan bir jSON dizesidir:
- id: Arduino'nun kimlik numarası;
- desc: Arduino'nun neler yapabileceğine dair bir açıklama. Burada sadece Arduino'nun türünü belirtmişiz;
- mac: Arduino'nun MAC adresi;
- port: Arduino'nun [DAO] katmanından gelecek komutları bekleyeceği bağlantı noktası numarası.
Tüm bu bilgiler karakter dizisi türündedir; ancak port, bir tamsayıdır.
- Arduino, kayıt sunucusuna kaydolduktan sonra, sunucuya bildirdiği bağlantı noktasında (yukarıda 102) dinlemeye başlar. Aşağıdaki biçimdeki jSON komutlarını bekler:
Bu, aşağıdaki öğeleri içeren bir jSON dizesidir:
- id: siparişin kimlik numarası. Herhangi bir değer olabilir;
- ac: bir eylem. Üç tür vardır:
- pw (pin write): bir pime değer yazmak için,
- pr (pin read): bir pinden değer okumak için,
- cl (yanıp sönme), bir LED'i yanıp sönmek için;
- pa: eylemin parametreleri. Bunlar eyleme göre değişir.
- Arduino, istemcisine her zaman bir yanıt gönderir. Bu yanıt, aşağıdaki biçimdeki bir jSON dizesidir:
burada
- id: yanıt verilen komutun kimliği;
- er (hata): bir hata oluşmuşsa hata kodu, aksi takdirde 0;
- ve (durum): pr okuma komutu hariç, her zaman boş olan bir sözlük. Bu durumda sözlük, istenen x numaralı pinin değerini içerir.
Aşağıda, yukarıdaki özellikleri açıklığa kavuşturmak amacıyla bazı örnekler verilmiştir:
8 numaralı LED'i 100 milisaniyelik aralıklarla 10 kez yanıp sönmesini sağlamak:
Komut | |
Yanıt |
cl komutundaki pa parametreleri şunlardır: bir yanıp sönmenin süresi (milisaniye cinsinden), yanıp sönme sayısı (nb) ve LED'in pin numarası.
7 numaralı pime 1 ikili değerini yazın:
Komut | |
Yanıt |
pw komutunun pa parametreleri şunlardır: yazma modu b (ikili) veya a (analog), yazılacak val değeri, pinin numarası. İkili yazma için val değeri 0 veya 1'dir. Analog yazma için val değeri [0,255] aralığındadır.
2 numaralı pime 120 analog değerini yazmak için:
Komut | |
Yanıt |
0 numaralı pinden analog değeri oku:
Komut | |
Yanıt |
pr komutundaki pa parametreleri şunlardır: okuma modu mod b (ikili) veya a (analog), pinin numarası. Hata yoksa, Arduino yanıtının "et" kısmına istenen pinin değerini ekler. Burada pin0, 0 numaralı pinin değerinin istendiğini, 1023 ise bu değeri belirtir. Okuma işleminde, analog bir değer [0, 1024] aralığında olacaktır.
cl, pw ve pr adlı üç komutu tanıttık. jSON dizelerinde neden daha açıklayıcı alanlar kullanılmadığı merak edilebilir: ac yerine action, pw yerine pinwrite, pa yerine parametres gibi... Bir Arduino’nun belleği oldukça sınırlıdır. Oysa Arduino ile alışverişi yapılan jSON dizeleri bellek kullanımına katkıda bulunuyor. Bu nedenle bu dizeleri olabildiğince kısaltmayı tercih ettik.
Şimdi birkaç hata örneğine bakalım:
Komut | |
Yanıt |
jSON biçiminde olmayan bir komut gönderildi. Arduino, 100 hata kodunu geri gönderdi.
Komut | |
Yanıt |
pin parametresini atlayarak bir pr komutu gönderildi. Arduino, 302 hata kodunu döndürdü.
Komut | |
Yanıt |
Bilinmeyen bir pinread komutu (pr) gönderildi. Arduino, 104 hata kodunu geri gönderdi.
Örneklere devam etmeyeceğiz. Kural basit. Arduino'ya gönderilen komut ne olursa olsun, Arduino çökmemelidir. jSON komutunu yürütmeden önce, komutun doğru olup olmadığını kontrol eder. Bir hata ortaya çıkar çıkmaz, Arduino komutun yürütülmesini durdurur ve müşterisine jSON hata dizesini geri gönderir. Burada da, bellek alanı kısıtlı olduğu için, tam bir mesaj yerine bir hata kodu gönderilir.
Arduino üzerinde çalıştırılan programın kodu, bu belgedeki örneklerde size sunulmuştur:
![]() |
Bunu Arduino'ya aktarmak için:
- Arduino'yu PC'e bağlayın;
![]() | ![]() |
- [1]'te [arduino_uno.ino] dosyasını açın. IDE Arduino çalışacak ve dosyayı yükleyecektir;
Not: Kod, başlangıçta IDE ARDUINO 1.5.x sürümüyle oluşturulmuş ve test edilmiştir. O zamandan beri IDE'in başka sürümleri de piyasaya çıkmıştır. Kod, IDE ARDUINO 1.6.x sürümünde çalışmadı. Görünüşe göre 1.6 ve 1.5 sürümleri arasında geriye dönük uyumluluk sorunu var.
- [2-4]'te, kullanılan Arduino türünü belirtin;
![]() | ![]() |
- [5-7]'te, PC'in hangi seri bağlantı noktasında bulunduğunu belirtin;
- [8]'te, [arduino_uno] programını Arduino'ya yükleyin;
Program kodunda çok sayıda açıklama bulunmaktadır. İlgilenen okuyucular bu açıklamalara başvurabilirler. Biz sadece Arduino ile PC arasında iki yönlü istemci/sunucu iletişimini yapılandırmaya olanak tanıyan kod satırlarına dikkat çekmek istiyoruz:
#<SPI.h> dosyasını dahil et
#include <Ethernet.h>
#<ajSON.h> dosyasını dahil et
// ---------------------------------- CONFIGURATION DE L'ARDUINO UNO
// Arduino'nun MAC adresi UNO
byte macArduino[] = {
0x90, 0xA2, 0xDA, 0x0D, 0xEE, 0xC7 };
char * strMacArduino="90:A2:DA:0D:EE:C7";
// Arduino'nun adresi IP
IPAddress ipArduino(192,168,2,2);
// kimliği
char * idArduino="cuisine";
// Arduino sunucusunun bağlantı noktası
int portArduino=102;
// Arduino'nun açıklaması
char * descriptionArduino="contrôle domotique";
// Arduino sunucusu 102 numaralı bağlantı noktasında çalışacak
EthernetServer server(portArduino);
// kayıt sunucusunun IP
IPAddress ipServeurEnregistrement(192,168,2,1);
// kayıt sunucusunun bağlantı noktası
int portServeurEnregistrement=100;
// kayıt sunucusunun Arduino istemcisi
EthernetClient clientArduino;
// istemcinin komutu
char commande[100];
// Arduino'nun yanıtı
char message[100];
// başlatma
void setup() {
// Seri monitör, veri alışverişini takip etmenizi sağlayacaktır
Serial.begin(9600);
// Ethernet bağlantısının başlatılması
Ethernet.begin(macArduino,ipArduino);
// kullanılabilir bellek
Serial.print(F("Memoire disponible : "));
Serial.println(freeRam());
}
// sonsuz döngü
void loop()
{
...
}
- 8. satır: Arduino'nun MAC adresi. Arduino, bir PC ve bir veya daha fazla Arduino'nun bulunduğu özel bir ağda yer alacağı için bu adres burada çok önemli değildir. Önemli olan, bu özel ağda MAC adresinin benzersiz olmasıdır. Normalde, Arduino'nun ağ kartında kartın MAC adresinin yazılı olduğu bir etiket bulunur. Bu etiket yoksa ve kartın MAC adresini bilmiyorsanız, özel ağda MAC adresinin benzersizlik kuralına uyulduğu sürece 8. satıra istediğiniz değeri girebilirsiniz;
- 11. satır: Kartın IP adresi. Yine, [192.168.2.x] gibi istediğiniz bir değer girin ve özel ağdaki farklı Arduino'lar için x değerini değiştirin;
- 13. satır: Arduino'nun kimliği. Aynı özel ağdaki Arduino'ların kimlikleri arasında benzersiz olmalıdır;
- 15. satır: Arduino'nun hizmet bağlantı noktası. İstediğiniz değeri girebilirsiniz;
- satır 17: Arduino'nun işlevinin açıklaması. İstediğiniz herhangi bir şey yazabilirsiniz. Arduino'nun sınırlı belleği nedeniyle uzun dizelere dikkat edin;
- satır 21: IP sunucusundaki Arduino kayıt sunucusunun adresi. Değiştirilmemelidir;
- 23. satır: Bu kayıt hizmetinin bağlantı noktası. Değiştirilmemelidir;
5.4. Web sunucusu / jSON
5.4.1. Kurulum

/ jSON web sunucusunun Java ikili dosyası size verilmiştir:
![]() |
Bir komut penceresi açın ve aşağıdaki komutu yazın:
[java.exe] komut penceresindeki PATH içinde bulunmuyorsa, [java.exe] dosyasının tam yolunu (genellikle C:\Program Files\java\...) yazmanız gerekecektir.
Bir DOS penceresi açılacak ve şu günlükleri gösterecektir:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v0.5.0.M6)
2014-01-06 11:11:35.550 INFO 8408 --- [ main] arduino.rest.metier.Application : Starting Application on Gportpers3 with PID 8408 (C:\Users\SergeTahÚ\Desktop\part2\server.jar started by ST)
2014-01-06 11:11:35.587 INFO 8408 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6a4ba620: startup date [Mon Jan 06 11:11:35 CET 2014]; root of context hierarchy
2014-01-06 11:11:36.765 INFO 8408 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2014-01-06 11:11:36.766 INFO 8408 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.42
2014-01-06 11:11:36.876 INFO 8408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2014-01-06 11:11:36.877 INFO 8408 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1293 ms
2014-01-06 11:11:37.084 INFO 8408 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2014-01-06 11:11:37.084 INFO 8408 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2014-01-06 11:11:37.184 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-01-06 11:11:37.386 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/blink/{idCommande}/{idArduino}/{pin}/{duree}/{nombre}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.faireClignoterLed(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.388 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/commands/{idArduino}],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.sendCommandesJson(java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.388 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.getArduinos(javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.389 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.pinRead(java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.390 INFO 8408 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{valeur}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String arduino.rest.metier.RestMetier.pinWrite(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,javax.servlet.http.HttpServletResponse)
2014-01-06 11:11:37.463 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] türündeki işleyiciye
2014-01-06 11:11:37.464 INFO 8408 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] QZXW2HTML türündeki işleyici BW2NsYXNzIG9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQucmVzb3VyY2UuUmVzb3VyY2VIdHRwUmVxdWVzdEhhbmRsZXJdZQX
2014-01-06 11:11:37.881 INFO 8408 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 796 ms
Serveur d'enregistrement lancÚ sur 192.168.2.1:100
2014-01-06 11:11:38.101 INFO 8408 --- [ Thread-4] arduino.dao.Recorder : Recorder : [11:11:38:101] : [Serveur d'enregistrement : attente d'un client]
2014-01-06 11:11:38.142 INFO 8408 --- [ main] arduino.rest.metier.Application : Started Application in 3.257 seconds
- 11. satır: Yerleşik bir Tomcat sunucusu başlatılır;
- 15. satır: Spring MVC servletinin [dispatcherServlet] servleti yüklenir ve çalıştırılır;
- 18. satır: URL Rest [/arduinos/blink/{idCommande}/{idArduino}/{pin}/{duree}/{nombre}] algılanır;
- 19. satır: URL Rest [/arduinos/commands/{idArduino}] algılanır;
- 20. satır: URL Rest [/arduinos/] algılanır;
- satır 21: URL Rest [/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}] algılanır;
- satır 22: URL Rest [/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{valeur}] algılandı;
- satır 26: Arduino kayıt sunucusu başlatıldı;
Henüz yapmadıysanız, Arduino'nuzu PC'e bağlayın. PC'in güvenlik duvarı devre dışı bırakılmalıdır. Ardından bir tarayıcıyla URL [http://localhost:8080/arduinos] adresini girin:
![]() |
Bağlı olan Arduino'nun kimlik numarası ekranda görünmelidir. Herhangi bir şey görmüyorsanız, Arduino'yu sıfırlamayı deneyin. Bunun için bir düğmesi bulunmaktadır.
Web sunucusu / jSON artık kurulmuştur.
5.4.2. / jSON web hizmeti tarafından sunulan URL
Okunması gerekenler: [Exemple-15] projesi (bkz. paragraf 1.16.1);
/ jSON web hizmeti, Spring MVC ile uygulanmıştır ve aşağıdaki URL'leri sunar:
@Controller
public class WebController {
// iş katmanı
@Autowired
private IMetier métier;
// Arduino listesi
@RequestMapping(value = "/arduinos", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getArduinos() throws JsonProcessingException {
...
}
// yanıp sönme
@RequestMapping(value = "/arduinos/blink/{idCommande}/{idArduino}/{pin}/{duree}/{nombre}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String faireClignoterLed(@PathVariable("idCommande") String idCommande, @PathVariable("idArduino") String idArduino, @PathVariable("pin") int pin, @PathVariable("duree") int duree, @PathVariable("nombre") int nombre) throws JsonProcessingException {
...
}
// komut gönderme JSON
@RequestMapping(value = "/arduinos/commands/{idArduino}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String sendCommandesJson(@PathVariable("idArduino") String idArduino, HttpServletRequest request) throws IOException {
...
}
// pin okuma
@RequestMapping(value = "/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String pinRead(@PathVariable("idCommande") String idCommande, @PathVariable("idArduino") String idArduino, @PathVariable("pin") int pin, @PathVariable("mode") String mode) throws JsonProcessingException {
....
}
// pin yazma
@RequestMapping(value = "/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{valeur}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String pinWrite(@PathVariable("idCommande") String idCommande, @PathVariable("idArduino") String idArduino, @PathVariable("pin") int pin, @PathVariable("mode") String mode, @PathVariable("valeur") int valeur) throws JsonProcessingException {
...
}
}
Sunucu tarafından gönderilen yanıtlar, aşağıdaki [Response<T>] sınıfının jSON temsilidir:
package client.android.dao.service;
import java.util.List;
public class Response<T> {
// ----------------- özellikler
// işlem durumu
private int status;
// olası durum mesajları
private List<String> messages;
// yanıt gövdesi
private T body;
// oluşturucular
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// alıcı ve ayarlayıcılar
...
}
URL, [/arduinos]'e [Response<List<Arduino>>] türünde bir yanıt gönderir; burada [Arduino] aşağıdaki sınıftır:
package android.arduinos.entities;
import java.io.Serializable;
public class Arduino implements Serializable {
// veriler
private String id;
private String description;
private String mac;
private String ip;
private int port;
// getter ve setter'lar
...
}
- 7. satır: [id], Arduino'nun kimliğidir;
- 8. satır: açıklaması;
- 9. satır: adresi MAC;
- 10. satır: adresi IP;
- 11. satır: Komutları beklediği bağlantı noktası;
URL:
- [/arduinos/blink/{idCommande}/{idArduino}/{pin}/{duree}/{nombre}];
- [/arduinos/pinRead/{idCommande}/{idArduino}/{pin}/{mode}];
- [/arduinos/pinWrite/{idCommande}/{idArduino}/{pin}/{mode}/{valeur}];
- [/arduinos/commands/{idArduino}];
[Response<ArduinoResponse>] türünde bir yanıt gönderir; burada [ArduinoResponse] sınıfı, bir Arduino'nun standart yanıtını temsil eder:
public class ArduinoResponse implements Serializable {
private String json;
private String id;
private String erreur;
private Map<String, Object> etat;
// getter ve setter'lar
...
}
- [json]: Bir Arduino tarafından gönderilen ve kod çözülemeyen jSON dizesi (hata durumu), aksi takdirde null;
- [id]: Arduino'nun yanıt verdiği komutun kimliği;
- [erreur]: bir hata kodu; OK ise 0, aksi takdirde başka bir değer;
- [etat]: komuta özgü yanıtı içeren bir sözlük. Komut, Arduino'dan bir değerin okunmasını istemediği sürece genellikle boştur; böyle bir durumda değer bu sözlüğe yerleştirilir;
5.4.3. Web hizmeti testleri / jSON
Aşağıdaki URL komutlarını test ederek / jSON web sunucusuna alışın:
İşte elde etmeniz gereken sonuçların birkaç ekran görüntüsü:
Bağlı Arduino'ların listesini alma:
![]() |
Web sunucusundan alınan jSON / jSON dizesi, aşağıdaki alanlara sahip bir nesnedir:
- [status]: 0 ise hata olmadığını, aksi takdirde hata olduğunu gösterir;
- [messages]: Hata oluşmuşsa, hatayı açıklayan mesajların listesi:
- [body]: Hata oluşmamışsa Arduino'ların listesi. Her bir Arduino, aşağıdaki alanlara sahip bir nesne ile tanımlanır:
- [id]: Arduino'nun kimlik numarası. İki Arduino'nun kimlik numarası aynı olamaz;
- [description]: Arduino'nun işlevine ilişkin kısa açıklama;
- [mac]: Arduino'nun MAC adresi;
- [ip]: Arduino'nun IP adresi;
- [port]: komutları beklediği bağlantı noktası;
[cuisine] ile tanımlanan Arduino'nun 8 numaralı pininin LED'ini her 100 ms'de bir 20 kez yanıp sönmesini sağlamak:
![]() |
Web sunucusundan alınan jSON dizesi / jSON, aşağıdaki alanlara sahip bir nesnedir:
- [status]: 0 ise hata olmadığını, aksi takdirde hata olduğunu gösterir;
- [messages]: Hata oluşması durumunda hatayı açıklayan mesajların listesi:
- [body]: Hata olmaması durumunda Arduino'nun yanıtı:
- [id]: komut kimliği. Bu kimlik, [/blink/1]'teki 1'dir. Arduino, yanıtında bu komut kimliğini tekrar kullanır;
- [erreur]: bir hata numarası. 0'dan farklı bir değer, bir hata olduğunu gösterir;
- [etat]: Yalnızca bir pin değerini okumak için kullanılır. Bu durumda değeri, ilgili pin değeridir;
- [json]: Yalnızca istemci ile sunucu arasında jSON hatası oluştuğunda kullanılır. Bu durumda değeri, Arduino tarafından gönderilen hatalı jSON dizesidir;
[cuisine] ile tanımlanan Arduino'nun 0 numaralı pininden analog okuma:
![]() |
Web sunucusundan alınan jSON dizesi / jSON, 0 numaralı pinin değerini temsil eden [etat] alanı hariç olmak üzere, öncekiyle aynıdır.
[cuisine] ile tanımlanan Arduino'nun 5 numaralı pininden alınan ikili okuma:
![]() |
Web sunucusundan alınan jSON dizesi / jSON, öncekine benzerdir.
[cuisine] ile tanımlanan Arduino'nun 8 numaralı pinine 1 değerinin ikili olarak yazılması:
![]() |
Web sunucusundan alınan jSON dizesi / jSON, öncekine benzerdir.
URL ve [http://localhost:8080/arduinos/commands/cuisine] testleri ise daha karmaşıktır. Bu URL'i işleyen / jSON web sunucusu yöntemi, bir tarayıcıyla basitçe simüle edilemeyen bir POST isteği beklemektedir. Bu URL'i test etmek için, [Advanced REST Client] uzantısına sahip bir Chrome tarayıcısı kullanılabilir (bkz. paragraf 6.13):
![]() |
- [1]'te, test edilecek web yönteminin URL'i;
- [2]'e, isteği göndermek için POST yöntemi;
- [3-4]'te, gönderilen değer jSON'tir;
- [5]'te, gönderilen dize jSON'tir. Listenin başında ve sonunda bulunan köşeli parantezlere dikkat edilmelidir. Burada, listede sadece 8 numaralı pini her 100 ms'de bir 10 kez yanıp sönmesini sağlayan jSON komutu bulunmaktadır;
- [6] komutunda istek gönderilir;
![]() |
- [7] komutunda, sunucu tarafından gönderilen jSON yanıtı. Nesne, her zamanki iki alan olan [status, messages] ile birlikte, değeri gönderilen her bir jSON komutuna Arduino'nun verdiği yanıtların listesi olan [body] alanını içeren bir nesne aldı.
Arduino için sözdizimi açısından hatalı bir jSON komutu gönderildiğinde ne olduğunu görelim:
![]() |
Bunun üzerine şu yanıt alınır:
![]() |
Arduino'nun yanıtında hata numarasının [104] olduğunu görüyoruz; bu, [xx] komutunun tanınmadığını gösteriyor.
5.5. Android istemcisi testleri
![]() |
Tamamlanmış Android istemcisinin çalıştırılabilir dosyası aşağıda verilmiştir:
![]() |
Fareyi kullanarak yukarıdaki [app-debug.apk] dosyasını [GenyMotion] tablet emülatörüne sürükleyip bırakın. Dosya kaydedilip çalıştırılacaktır. Henüz yapmadıysanız, /jSON web sunucusunu da başlatın. Üzerinde bir LED bulunan Arduino'yu PC'e bağlayın. Android istemcisi, Arduino'ları uzaktan yönetmenizi sağlar. Kullanıcıya aşağıdaki ekranları gösterir.
[CONFIG] sekmesi, sunucuya bağlanmanıza ve bağlı Arduino'ların listesini almanıza olanak tanır:

- [1] sekmesine, IP [192.168.2.1] cihazınıza verilen adresi girin (bkz. paragraf 5.2).
[PINWRITE] sekmesi, bir Arduino'nun bir pinine bir değer yazmanıza olanak tanır:


[PINREAD] sekmesi, bir Arduino pininin değerini okumaya olanak tanır:

[BLINK] sekmesi, bir Arduino'daki LED'i yanıp sönmesini sağlar:

[COMMAND] sekmesi, bir Arduino'ya jSON komutunu göndermenizi sağlar:

5.6. Web hizmetinin Android istemcisi / jSON
Şimdi Android istemcisinin yazımına geçiyoruz.
5.6.1. İstemcinin mimarisi
Android istemcisinin mimarisi, [Exemple-15] projesininkiyle aynı olacaktır (bkz. paragraf 1.16.2);
![]() |
- [DAO] katmanı, web sunucusu / jSON ile iletişim kurar;
Android istemcisi, birden fazla Arduino'yu aynı anda kontrol edebilmelidir. Örneğin, iki Arduino'ya yerleştirilmiş iki LED'i arka arkaya değil, aynı anda yanıp sönmesini sağlamak istiyoruz. Bu nedenle Android istemcimiz, her Arduino için bir asenkron görev kullanacak ve bu görevler paralel olarak çalışacaktır.
5.6.2. İstemcinin Android Studio projesi
[client-android-skel] projesini (bkz. 2. paragraf) [client-arduinos-01] projesine kopyalayın (gerekirse, 1.15. paragrafta bir Gradle projesinin nasıl kopyalanacağını tekrar gözden geçirin):

5.6.3. Beş görünüm XML
![]() |
Beş görünüm olacaktır XML:
- [blink]: Bir Arduino'daki LED'i yanıp sönmek için. Bu, [BlinkFragment] parçasıyla ilişkilidir;
- [commands]: Bir Arduino'ya jSON komutunu göndermek için. Bu, [CommandsFragment] parçasıyla ilişkilidir;
- [config]: /URL web hizmetini yapılandırmak ve bağlı Arduino'ların ilk listesini almak için kullanılır. Bu komut, [ConfigFragment] parçasıyla ilişkilidir;
- [pinread]: Bir Arduino pininin ikili veya analog değerini okumak için kullanılır. [PinReadFragment] parçasıyla ilişkilidir;
- [pinwrite]: Bir Arduino pini üzerine ikili veya analog bir değer yazmak için kullanılır. Bu, [PinWriteFragment] parçasıyla ilişkilidir;
Şu anda, bu beş XML görünümü hepsi aynı boş içeriğe sahip olacaktır:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
</ScrollView>
- Görünüm, [RelativeLayout] adlı bir kapsayıcıda (satır 7-10) yer alır ve bu kapsayıcı da [ScrollView] adlı bir kapsayıcının (satır 2-11) içinde bulunur. Bu, görünümün bir tablet ekranının boyutunu aşması durumunda görünümde 'kaydırma' yapabilmemizi sağlar;
Görev: Beş adet XML görünümünü oluşturun.
5.6.4. Parça menüsü
[client-android-skel] ile oluşturulan bir projedeki parçaların, boş da olsa bir menüyle ilişkilendirilmesi gerektiğini biliyoruz. Burada, uygulamanın bir menüsü olmayacak. Boş menü zaten projede mevcut;
![]() |
5.6.5. Uygulamanın beş parçası
![]() | ![]() |
Görev: [DummyFragment] parçasını, [2]'te gösterildiği gibi uygulamanın beş parçasına kopyalayın.
[ConfigFragment] parçası aşağıdaki iskelete sahiptir:
package client.android.fragments.behavior;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.custom.CoreState;
import client.android.fragments.state.DummyFragmentState;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsMenu;
@EFragment
@OptionsMenu(R.menu.menu_vide)
public class ConfigFragment extends AbstractFragment {
// üst sınıftan miras alınan alanlar -------------------------------------------------------
...
- satırı aşağıdaki satırla değiştirin:
@EFragment(R.layout.config)
Görev: Sınıfın [@EFragment] özniteliğini uyarlayarak diğer dört parça için de aynısını yapın.
Parça | Görünüm |
ConfigFragment | |
PinReadFragment | |
PinWriteFragment | |
CommandsFragment | |
BlinkFragment | |
5.6.6. Parça durumları
![]() | ![]() |
Her parçanın bir durumu olacaktır.
Görev: [DummyFragmentState] sınıfını beş kez çoğaltarak, [2]'te gösterilen beş durumu oluşturun.
5.6.7. Projenin özelleştirilmesi
![]() |
[architecture / custom] paketi, uygulama mimarisinin özelleştirilebilir öğelerini içerir.
5.6.7.1. [IMainActivity] arayüzü
[IMainActivity] arayüzü, parçaların etkinlikten ne talep edebileceğini ve uygulama sabitlerini tanımlar. Bu arayüz şu şekilde olacaktır:
package client.android.architecture.custom;
import client.android.architecture.core.ISession;
import client.android.dao.service.IDao;
public interface IMainActivity extends IDao {
// oturuma erişim
ISession getSession();
// görünüm değiştirme
void navigateToView(int position, ISession.Action action);
// bekleme yönetimi
void beginWaiting();
void cancelWaiting();
// uygulama sabitleri -------------------------------------
// hata ayıklama modu
boolean IS_DEBUG_ENABLED = true;
// sunucu yanıtı için maksimum bekleme süresi
int TIMEOUT = 1000;
// istemci isteğinin yürütülmesinden önceki bekleme süresi
int DELAY = 000;
// temel kimlik doğrulama
boolean IS_BASIC_AUTHENTIFICATION_NEEDED = false;
// parçaların bitişikliği
int OFF_SCREEN_PAGE_LIMIT = 1;
// sekme çubuğu
boolean ARE_TABS_NEEDED = true;
// bekleme görüntüsü
boolean IS_WAITING_ICON_NEEDED = true;
// parça sayısı
int FRAGMENTS_COUNT = 5;
// görüntüleme sayısı
int VUE_CONFIG = 0;
int VUE_BLINK = 1;
int VUE_PINREAD = 2;
int VUE_PINWRITE = 3;
int VUE_COMMANDS = 4;
}
- 25, 28, 31, 40. satırlar: [DAO] katmanının yapılandırması. Bu uygulama bir web sunucusuna / jSON sorgu gönderir;
- 37. satır: Bu uygulamanın sekmeleri vardır;
- 43. satır: Bu uygulamanın beş parçası vardır;
- satır 46-50: beş parçanın numaraları;
- satır 34: parçaların bitişikliği. Geliştirici buraya [1, FRAGMENTS_COUNT-1] aralığında bir değer girebilir;
5.6.7.2. [CoreState] sınıfı
[CoreState] sınıfı, parça durumlarının üst sınıfıdır:
package client.android.architecture.custom;
import client.android.architecture.core.MenuItemState;
import client.android.fragments.state.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConfigFragmentState.class),
@JsonSubTypes.Type(value = BlinkFragmentState.class),
@JsonSubTypes.Type(value = PinReadFragmentState.class),
@JsonSubTypes.Type(value = PinWriteFragmentState.class),
@JsonSubTypes.Type(value = CommandsFragmentState.class)}
)
public class CoreState {
// parçanın ziyaret edilip edilmediği
protected boolean hasBeenVisited = false;
// parçanın menüsünün durumu (varsa)
protected MenuItemState[] menuOptionsState;
// getter ve setter'lar
...
}
- 12-16. satırlar: Burada beş parçanın durum sınıfları tanımlanmalıdır;
5.6.8. [MainActivity] sınıfı
![]() |
[MainActivity] sınıfı şu şekilde olacaktır:
package client.android.activity;
import android.support.design.widget.TabLayout;
import android.util.Log;
import client.android.R;
import client.android.architecture.core.AbstractActivity;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.custom.IMainActivity;
import client.android.architecture.custom.Session;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.ArduinoCommand;
import client.android.dao.entities.ArduinoResponse;
import client.android.dao.service.Dao;
import client.android.dao.service.IDao;
import client.android.dao.service.Response;
import client.android.fragments.behavior.*;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.OptionsMenu;
import rx.Observable;
import java.util.List;
import java.util.Locale;
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
// [DAO] katmanı
@Bean(Dao.class)
protected IDao dao;
// oturum
private Session session;
// üst sınıf yöntemleri -----------------------
@Override
protected void onCreateActivity() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d(className, "onCreateActivity");
}
// oturum
this.session = (Session) super.session;
// beş sekmenin oluşturulması
for (int i = 0; i < 5; i++) {
TabLayout.Tab newTab = tabLayout.newTab();
newTab.setText(getFragmentTitle(i));
tabLayout.addTab(newTab);
}
}
@Override
protected IDao getDao() {
return dao;
}
@Override
protected AbstractFragment[] getFragments() {
return new AbstractFragment[]{new ConfigFragment_(), new BlinkFragment_(), new PinReadFragment_(), new PinWriteFragment_(), new CommandsFragment_()};
}
@Override
protected CharSequence getFragmentTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.config_titre).toUpperCase(l);
case 1:
return getString(R.string.blink_titre).toUpperCase(l);
case 2:
return getString(R.string.pinread_titre).toUpperCase(l);
case 3:
return getString(R.string.pinwrite_titre).toUpperCase(l);
case 4:
return getString(R.string.commands_titre).toUpperCase(l);
}
return null;
}
@Override
protected void navigateOnTabSelected(int position) {
// konum numarası olan parçayı görüntüleme
navigateToView(position, ISession.Action.NAVIGATION);
}
@Override
protected int getFirstView() {
return IMainActivity.VUE_CONFIG;
}
// uygulama IDao -----------------------------------------
}
- 46-50. satırlar: Uygulamanın beş sekmesinin oluşturulması;
- 48. satır: sekme başlıkları, 63-79. satırlardaki yöntemle sağlanır;
- beş parça, 60. satırda örneklenir. AA ek açıklamaları nedeniyle, parçaların sınıfları daha önce sunulanların altına bir alt çizgi eklenmiş halidir;
- 63-79. satırlar: Her bir parça için bir başlık tanımlanır. Bu başlıklar [res / values / strings.xml] dosyasında aranacaktır
![]() |
[strings.xml] dosyasının içeriği şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- uygulama adı -->
<string name="app_name">[arduinos-client-01]</string>
<!-- Parçalar ve sekmeler -->
<string name="config_titre">[Config]</string>
<string name="blink_titre">[Blink]</string>
<string name="pinread_titre">[PinRead]</string>
<string name="pinwrite_titre">[PinWrite]</string>
<string name="commands_titre">[Commands]</string>
</resources>
Ödev: Yukarıdaki öğeleri oluşturun ve projeyi derleyin. Hata olmamalıdır.
Projeyi çalıştırın. Emülatörde aşağıdaki görünümle karşılaşmalısınız:

İlk görünümün görüntülenmesiyle birlikte gelen günlükleri inceleyin ve yürütülen farklı adımların izini takip edin. Sekmeler arasında geçiş yapın ve günlükleri takip etmeye devam edin.
5.6.9. XML [config]
XML [config] görünümü şu şekilde olacaktır:
![]() | ![]() |
Yukarıdaki görünüm, aşağıdaki XML kodu ile elde edilir:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_TitreConfig"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dp"
android:text="@string/txt_TitreConfig"
android:textSize="@dimen/titre"/>
<TextView
android:id="@+id/txt_UrlServiceRest"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_TitreConfig"
android:layout_marginTop="50dp"
android:text="@string/txt_UrlServiceRest"
android:textSize="20sp"/>
<EditText
android:id="@+id/edt_UrlServiceRest"
android:layout_width="300dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_UrlServiceRest"
android:layout_alignBottom="@+id/txt_UrlServiceRest"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_UrlServiceRest"
android:ems="10"
android:hint="@string/hint_UrlServiceRest"
android:inputType="textUri">
<requestFocus/>
</EditText>
<TextView
android:id="@+id/txt_MsgErreurIpPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_UrlServiceRest"
android:layout_marginTop="20dp"
android:text="@string/txt_MsgErreurUrlServiceRest"
android:textColor="@color/red"
android:textSize="20sp"/>
<TextView
android:id="@+id/txt_arduinos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_MsgErreurIpPort"
android:layout_marginTop="40dp"
android:text="@string/titre_list_arduinos"
android:textColor="@color/blue"
android:textSize="20sp"/>
<Button
android:id="@+id/btn_Rafraichir"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_arduinos"
android:layout_alignBottom="@+id/txt_arduinos"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduinos"
android:text="@string/btn_rafraichir"/>
<Button
android:id="@+id/btn_Annuler"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_arduinos"
android:layout_alignBottom="@+id/txt_arduinos"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduinos"
android:text="@string/btn_annuler"
android:visibility="invisible"/>
<ListView
android:id="@+id/ListViewArduinos"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_arduinos"
android:layout_marginTop="30dp"
android:background="@color/wheat">
</ListView>
</RelativeLayout>
</ScrollView>
Görünüm, android:text dosyasında tanımlanan karakter dizelerini (15, 25, 37, 50, 61, 73. satırlarda) kullanır:
![]() |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">android-domotique</string>
<!-- Parçalar ve sekmeler -->
<string name="config_titre">[Config]</string>
<string name="blink_titre">[Blink]</string>
<string name="pinread_titre">[PinRead]</string>
<string name="pinwrite_titre">[PinWrite]</string>
<string name="commands_titre">[Commands]</string>
<!-- Yapılandırma -->
<string name="txt_TitreConfig">Se connecter au serveur</string>
<string name="txt_UrlServiceRest">Url du service web / jSON</string>
<string name="txt_MsgErreurUrlServiceRest">L\'Url du service doit être entrée sous la forme Ip1.Ip2.Ip3.IP4:Port/contexte</string>
<string name="hint_UrlServiceRest">ex (192.168.1.120:8080/rest)</string>
<string name="btn_annuler">Annuler</string>
<string name="btn_rafraichir">Rafraîchir</string>
<string name="titre_list_arduinos">Liste des Arduinos connectés</string>
</resources>
Görünüm, [res / values / colors] dosyasında tanımlanan renkleri (android:textColor, 51. ve 62. satırlar) kullanır:
![]() |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="floral_white">#FFFAF0</color>
<!-- uygulama -->
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
</resources>
Görünüm, [res / values / dimens] dosyasında tanımlanan boyutları (16. satırdaki android:textSize) kullanır:
![]() |
<resources>
<!-- Android Tasarım kılavuzuna göre varsayılan ekran kenar boşlukları. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
<!-- uygulama -->
<dimen name="titre">30dp</dimen>
</resources>
Bu teknik tüm boyutlar için kullanılmamıştır. Ancak tavsiye edilen yöntem budur. Bu sayede boyutlar tek bir yerden değiştirilebilir.
Görev: Yukarıdaki öğeleri oluşturun.
Projenizi yeniden çalıştırın. Aşağıdaki görünümü elde etmelisiniz:

5.6.10. [ConfigFragment] parçası
![]() |
Yeni [config] görünümünü yönetmek için, [ConfigFragment] parçacığının kodu şu şekilde değişir:
package client.android.fragments.behavior;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.fragments.state.ConfigFragmentState;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.config)
@OptionsMenu(R.menu.menu_vide)
public class ConfigFragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.btn_Rafraichir)
protected Button btnRafraichir;
@ViewById(R.id.btn_Annuler)
protected Button btnAnnuler;
@ViewById(R.id.edt_UrlServiceRest)
protected EditText edtUrlServiceRest;
@ViewById(R.id.txt_MsgErreurIpPort)
protected TextView txtMsgErreurUrlServiceRest;
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
}
// fragman yaşam döngüsü yönetimi -------------------------------------
@Override
public CoreState saveFragment() {
return new ConfigFragmentState();
}
@Override
protected int getNumView() {
return IMainActivity.VUE_CONFIG;
}
@Override
protected void initFragment(CoreState previousState) {
}
@Override
protected void initView(CoreState previousState) {
// İlk ziyaret mi?
if(previousState==null){
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
}
}
@Override
protected void updateOnSubmit(CoreState previousState) {
}
@Override
protected void updateOnRestore(CoreState previousState) {
}
@Override
protected void notifyEndOfUpdates() {
// düğmeler
initButtons();
}
@Override
protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
}
// özel yöntemler --------------------------------------------
private void initButtons() {
// [Exécuter] düğmesi, [Annuler] düğmesinin yerini alır
btnAnnuler.setVisibility(View.INVISIBLE);
btnRafraichir.setVisibility(View.VISIBLE);
}
}
- 23-32. satırlar: görsel arayüz öğeleri;
- 58-60. satırlar: parçaya ilk ziyaret edildiğinde hata mesajı gizlenir;
- 73-76. satırlar: parça her görüntülendiğinde, [Annuler] düğmesi gizlenecek (82. satır) ve [Rafraîchir] düğmesi görüntülenecektir (86-87. satırlar). Nitekim bu uygulamada, asenkron bir işlem devam ederken bir fragman görüntülenemez ve dolayısıyla [Annuler] düğmesi görünür;
Görev: Yukarıdaki öğeleri oluşturun.
Bu yeni sürümü çalıştırın. İlk görünüm artık şu şekilde olmalıdır:

5.6.10.1. [Rafraîchir] düğmesi
Şu an için [Rafraîchir] düğmesine yapılan tıklamayı şu şekilde işleyeceğiz:
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
// bir görev başlatılacak - bekleme hazırlığı yapılıyor
beginWaiting(1);
}
@Click(R.id.btn_Annuler)
protected void doAnnuler() {
if (isDebugEnabled) {
Log.d(className, "Annulation demandée");
}
// asenkron görevleri iptal ediyoruz
cancelRunningTasks();
}
protected void beginWaiting(int numberOfRunningTasks) {
// görevlerin bekleme durumu hazırlanıyor
beginRunningTasks(numberOfRunningTasks);
// [Annuler] düğmesi, [Rafraîchir] düğmesinin yerini alıyor
btnRafraichir.setVisibility(View.INVISIBLE);
btnAnnuler.setVisibility(View.VISIBLE);
}
// parça yaşam döngüsü yönetimi -------------------------------------
...
@Override
protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
// düğmelerin başlangıç durumu
initButtons();
}
// özel yöntemler --------------------------------------------
private void initButtons() {
// [Exécuter] düğmesi, [Annuler] düğmesinin yerini alır
btnAnnuler.setVisibility(View.INVISIBLE);
btnRafraichir.setVisibility(View.VISIBLE);
}
- 1-5. satırlar: [Rafraîchir] düğmesine tıklandığında yürütülen yöntem;
- 4. satır: bekleme süreci başlatılır;
- 18. satır: başlatılacak asenkron görevlerin sayısı üst sınıfa aktarılır. Bekleme görüntüsü görünecektir;
- 20-21. satırlar: Bu bekleme işlemi sonucunda [Annuler] düğmesi görünür, [Rafraîchir] düğmesi kaybolur ve bekleme simgesi görünür. Başka hiçbir şey olmaz. Ancak kullanıcı [Annuler] düğmesine tıklayabilir. Bu durumda 7-14. satırlardaki yöntem çalıştırılır;
- 13. satır: üst sınıftan tüm görevleri iptal etmesi istenir. Sınıf bunu yapar ve karşılığında 25-29. satırlardaki yöntemi çağırarak tüm görevlerin tamamlandığını bildirir. [runningTasksHaveBeenCanceled] parametresinin değeri, görevlerin iptal edildiğini belirtmek için true olacaktır;
- 35-36. satırlar: [Annuler] düğmesi kaybolurken, [Rafraîchir] düğmesi yeniden görünecektir.
Görev: Bu değişiklikleri yapın ve ardından projeyi çalıştırın. [Rafraîchir] düğmesinin bekleme modunu başlattığını ve [Annuler] düğmesinin bunu durdurduğunu kontrol edin. Günlükleri inceleyin.
5.6.10.2. Girişlerin Doğrulanması
Önceki sürümde, URL girişinin geçerliliğini kontrol etmiyorduk. Bunu kontrol etmek için, [ConfigFragment] dosyasına aşağıdaki kodu ekliyoruz:
// girdilen değerler
private String urlServiceRest;
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
// girdiler kontrol ediliyor
if (!pageValid()) {
return;
}
// bir görev başlatılacak - bekleme hazırlığı yapılıyor
beginWaiting(1);
}
// girdiler kontrol ediliyor
private boolean pageValid() {
// başlangıçta hata mesajı yok
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
// sunucunun IP adresi ve bağlantı noktası alınmaktadır
urlServiceRest = String.format("http://%s", edtUrlServiceRest.getText().toString().trim());
// geçerliliğini kontrol ediyoruz
try {
URI uri = new URI(urlServiceRest);
String host = uri.getHost();
int port = uri.getPort();
if (host == null || port == -1) {
throw new Exception();
}
} catch (Exception ex) {
// hata mesajı görüntüleniyor
txtMsgErreurUrlServiceRest.setVisibility(View.VISIBLE);
// UI'e geri dönülür
return false;
}
// sorun yok
return true;
}
- 2. satır: URL girişi;
- 7-9. satırlar: herhangi bir işlem yapmadan önce, girilen verilerin geçerliliğini kontrol ediyoruz;
- 19. satır: Girilen URL alınır ve ona [http://] öneki eklenir;
- 22. satır: Bununla bir URI (Uniform Resource Identifier) nesnesi oluşturulmaya çalışılır. Girilen URL sözdizimi açısından hatalıysa, bir istisna oluşur;
- 23-27. satırlar: URI doğruysa, ancak yine de [host==null] ve [port==-1] varsa bir istisna oluşturulur. Bu, olası bir durumdur;
- 30. satır: Bir istisna oluştu. Hata mesajı görüntülenir;
- 32. satır: Sayfanın geçersiz olduğunu belirtmek için [false] döndürülür;
- 35. satır: herhangi bir hata oluşmadı. Sayfanın geçerli olduğunu belirtmek için [true] değerini döndürürüz;
Görev: Yukarıdaki öğeleri oluşturun.
Bu yeni sürümü test edin ve geçersiz URL'lerin doğru şekilde bildirildiğini kontrol edin.
5.6.10.3. Arduino listesinin görüntülenmesi
![]() |
Farklı görünümler, bağlı Arduino'ların listesini görüntülemek zorunda kalacak. Bunun için, farklı sınıflar ve bir XML görünümü tanımlayacağız:
- Bir Arduino, [Arduino] ve [1] sınıflarıyla temsil edilecektir;
- [CheckedArduino] [1] sınıfı, bir listeden Arduino'nun seçilip seçilmediğini belirlemek için bir boole değeri eklenmiş olan [Arduino] sınıfından miras alınmıştır;
[Arduino] sınıfı, sunucu tarafından halihazırda kullanılan ve 5.4.2. paragrafında tanıtılan sınıftır. Şu şekildedir:
package android.arduinos.entities;
import java.io.Serializable;
public class Arduino implements Serializable {
// veriler
private String id;
private String description;
private String mac;
private String ip;
private int port;
// alıcı ve ayarlayıcılar
...
}
- 7. satır: [id], Arduino'nun kimliğidir;
- 8. satır: Arduino'nun açıklaması;
- 9. satır: adresi MAC;
- 10. satır: adresi IP;
- 11. satır: komutları beklediği bağlantı noktası;
Bu sınıf, bağlı Arduino'ların listesi istendiğinde sunucudan alınan jSON dizesine karşılık gelir:
![]() |
[CheckedArduino] sınıfı, [Arduino] sınıfından miras alır:
package android.arduinos.entities;
public class CheckedArduino extends Arduino {
private static final long serialVersionUID = 1L;
// bir Arduino seçilebilir
private boolean isChecked;
// yapıcı
public CheckedArduino(Arduino arduino, boolean isChecked) {
// üst sınıf
super(arduino.getId(), arduino.getDescription(), arduino.getMac(), arduino.getIp(), arduino.getPort());
// yerel
this.isChecked = isChecked;
}
// getter ve setter'lar
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean isChecked) {
this.isChecked = isChecked;
}
}
- 3. satır: [CheckedArduino] sınıfı, [Arduino] sınıfından miras alır;
- 6. satır: Görüntülenen Arduino listesinde bir Arduino'nun seçilip seçilmediğini anlamamıza yardımcı olacak bir boole değeri eklenir;
[ConfigFragment]'te, bağlı Arduino'ların listesini aldığımızı simüle edeceğiz.
![]() |
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
..
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
// girişler kontrol edilir
if (!pageValid()) {
return;
}
// bir görev başlatılacak - bekleme hazırlığı
beginWaiting(1);
// Arduino listesini temizliyoruz
clearArduinos();
// Arduino listesi arka planda isteniyor
getArduinosInBackground();
}
private void getArduinosInBackground() {
...
}
// Arduino listesi sıfırlanıyor
private void clearArduinos() {
// boş bir liste oluşturuluyor
List<String> strings = new ArrayList<>();
// listeyi görüntüleme
listArduinos.setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, strings));
}
- 2. satır: Sunucuya bağlı Arduino'ları görüntüleyen ListView;
- 5. satır: Bağlı Arduino'ların listesini isteyen yöntem;
- 11. satır: üst sınıfa asenkron bir görev başlatılacağını bildirir;
- 12. satır: Şu anda görüntülenen Arduino listesi silinir;
- 15. satır: Bağlı Arduino'ların listesi arka planda bir görev olarak istenir;
- satır 23-28: şu anda görüntülenen Arduino listesini silen yöntem;
[getArduinosInBackground] yöntemi şu şekildedir:
private void getArduinosInBackground() {
// hayali bir Arduino listesi oluşturuluyor
List<Arduino> arduinos = new ArrayList<>();
for (int i = 0; i < 20; i++) {
arduinos.add(new Arduino("id" + i, "desc" + i, "mac" + i, "ip" + i, i));
}
// sunucudan gelen yanıtı simüle ediyoruz
Response<List<Arduino>> response = new Response<>();
response.setBody(arduinos);
// beklemeyi iptal ediyoruz
cancelWaitingTasks();
// düğmeler değiştiriliyor
initButtons();
// yanıtı işleniyor
consumeArduinosResponse(response);
}
- satır 3-6: 20 Arduino'dan oluşan bir liste oluşturulur;
- satır 8-9: Oluşturulan Arduino listesini kapsayacak [Response<List<Arduino>>] türünde bir yanıt oluşturulur (paragraf 5.4.2);
- 11. satır: bekleme işlemi iptal edilir;
- 13. satır: Düğmeleri başlangıç durumlarına geri getirilir;
- 15. satır: yanıtı işliyoruz;
[consumeArduinosResponse] yöntemi şu şekildedir:
// yanıtın görüntülenmesi
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// hata mı?
if (response.getStatus() != 0) {
// görüntüleme
showAlert(response.getMessages());
// kullanıcı arayüzüne geri dön
return;
}
// bir liste oluşturuluyor [CheckedArduino]
List<CheckedArduino> checkedArduinos = new ArrayList<>();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// görüntüleniyor
showArduinos(checkedArduinos);
}
- satır 4-11: sunucu tarafından gönderilen yanıtın hata koduna bakılır:
- 4. satır: Hata kodu sıfırdan farklıysa;
- 6. satır: Sunucu tarafından yanıtın [messages] alanında depolanan mesajlar görüntülenir;
- 8. satır: Kullanıcı arayüzüne geri dönülür;
- 11-16. satırlar: Hata yoksa, alınan Arduino listesini List<CheckedArduino> türüne dönüştürdükten sonra görüntülenir;
[showArduinos] yöntemi şu şekildedir:
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// Arduino listesinden bir String listesi oluşturulur
List<String> strings = new ArrayList<>();
for (CheckedArduino checkedArduino : checkedArduinos) {
strings.add(checkedArduino.toString());
}
// görüntülenir
listArduinos.setAdapter(new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, strings));
}
Ödev: Yukarıdaki değişiklikleri yapın ve projenizi çalıştırın.
[Rafraîchir] düğmesine tıkladığınızda aşağıdaki ekran görüntüsünü görmelisiniz:

[1] girişi kullanılmamaktadır. Dolayısıyla, beklenen biçime uygun olduğu sürece buraya istediğiniz herhangi bir şey yazabilirsiniz.
5.6.10.4. Arduino'yu görüntülemek için bir şablon
Şu anda, bağlı Arduino'lar [Config] görünümünde şu şekilde görüntülenmektedir:

Artık bunları şu şekilde görüntülemek istiyoruz:
![]()
- [1]'te, bir Arduino'yu seçmeye olanak tanıyan bir onay kutusu yer alacaktır. Seçilemeyen Arduino'ların listesini sunmak istendiğinde bu onay kutusu gizlenecektir;
- [2]'te, Arduino'nun kimliği;
- [3]'te, açıklaması;
Aşağıda, 1.20. paragrafındaki [exemple-19] ve [exemple-19B] projelerinde geliştirilen kavramlar ele alınmaktadır. Gerekirse bunları tekrar gözden geçirin.
Öncelikle, Arduino listesindeki bir öğeyi görüntüleyecek olan görünümü oluşturuyoruz:
![]() |
Yukarıdaki [listarduinos_item] görünümünün kodu şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheat"
android:orientation="vertical" >
<CheckBox
android:id="@+id/checkBoxArduino"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/txt_arduino_description" />
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_marginLeft="40dp"
android:text="@string/txt_arduino_id" />
<TextView
android:id="@+id/txt_arduino_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/TextView1"
android:text="@string/dummy"
android:textColor="@color/blue" />
<TextView
android:id="@+id/TextView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_arduino_id"
android:text="@string/txt_arduino_description" />
<TextView
android:id="@+id/txt_arduino_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/checkBoxArduino"
android:layout_alignTop="@+id/TextView2"
android:layout_toRightOf="@+id/TextView2"
android:text="@string/dummy"
android:textColor="@color/blue" />
</RelativeLayout>
- 9-15. satırlar: onay kutusu;
- 17-23. satırlar: [Id : ] metni;
- 25-33. satırlar: Arduino'nun kimliği buraya yazılacaktır;
- 35-43. satırlar: [Description : ] metni;
- 45-53. satırlar: Arduino'nun açıklaması buraya yazılacaktır;
Bu görünüm, [res / values / strings.xml] dosyasında tanımlanan metinleri (23, 32, 43. satırlar) kullanır:
<string name="dummy">XXXXX</string>
<!-- listarduinos_item -->
<string name="txt_arduino_id">Id : </string>
<string name="txt_arduino_description">Description : </string>
Görünüm ayrıca [res / values / colors.xml] dosyasında tanımlanan bir rengi (satır 33, 53) kullanır:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
<color name="floral_white">#FFFAF0</color>
</resources>
Arduino listesindeki bir öğenin görüntüleme yöneticisi
![]() |
[ListArduinosAdapter] sınıfı, Arduino listesindeki her bir öğeyi görüntülemek için [ListView] tarafından çağrılan sınıftır. Kod şöyledir:
package istia.st.android.vues;
import istia.st.android.R;
...
public class ListArduinosAdapter extends ArrayAdapter<CheckedArduino> {
// Arduino tablosu
private List<CheckedArduino> arduinos;
// çalışma ortamı
private Context context;
// Arduino listesindeki bir satırın görüntü düzeni kimliği
private int layoutResourceId;
// satırda onay kutusu olup olmadığı
private Boolean selectable;
// oluşturucu
public ListArduinosAdapter(Context context, int layoutResourceId, List<CheckedArduino> arduinos, Boolean selectable) {
// üst öğe
super(context, layoutResourceId, arduinos);
// bilgiler kaydedilir
this.arduinos = arduinos;
this.context = context;
this.layoutResourceId = layoutResourceId;
this.selectable = selectable;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
...
}
}
- 18. satır: Sınıfın oluşturucusu dört parametre kabul eder: çalışmakta olan aktivite, veri kaynağındaki her bir öğe için görüntülenecek görünümün kimliği, listeyi besleyen veri kaynağı ve her bir Arduino ile ilişkili onay kutusunun görüntülenip görüntülenmeyeceğini belirten bir boole değeri;
- 8-15. satırlar: Bu dört bilgi yerel olarak saklanır;
29. satır: [getView] yöntemi, [ListView] içinde [position] numaralı görünümü oluşturmak ve olaylarını yönetmekle görevlidir. Kod şu şekildedir:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// geçerli Arduino
final CheckedArduino arduino = arduinos.get(position);
// geçerli satır oluşturulur
View row = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, parent, false);
// [TextView] üzerindeki referansları alınıyor
TextView txtArduinoId = (TextView) row.findViewById(R.id.txt_arduino_id);
TextView txtArduinoDesc = (TextView) row.findViewById(R.id.txt_arduino_description);
// satırı dolduruyoruz
txtArduinoId.setText(arduino.getId());
txtArduinoDesc.setText(arduino.getDescription());
// CheckBox her zaman görünür değildir
CheckBox ck = (CheckBox) row.findViewById(R.id.checkBoxArduino);
ck.setVisibility(selectable ? View.VISIBLE : View.INVISIBLE);
if (selectable) {
// buna değer atanır
ck.setChecked(arduino.isChecked());
// tıklama işlenir
ck.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
arduino.setChecked(isChecked);
}
});
}
// satırı görüntülenir
return row;
}
- 2. satır: İlk parametre, oluşturulacak satırın [ListView] içindeki konumudur. Bu aynı zamanda yerel olarak depolanan Arduino listesindeki konumdur;
- 4. satır: Oluşturulan satıra eşleştirilecek Arduino'ya ait bir referans alınır;
- 6. satır: Geçerli satır, [listarduinos_item.xml] görünümünden oluşturulur;
- satır 8-9: iki [TextView]'e ait referanslar alınır;
- 11-12. satırlar: iki [TextView]'e değerleri atanır;
- 14. satır: onay kutusuna bir referans alınır;
- satır 15: başlangıçta oluşturucuya aktarılan [selectable] değerine göre, onay kutusu görünür veya görünmez hale getirilir;
- 16. satır: onay kutusu mevcutsa;
- 18. satır: ona mevcut Arduino'nun [isChecked] değerini atanır;
- satır 20-26: onay kutusuna yapılan tıklama işlenir;
- 23. satır: onay kutusunun değeri mevcut Arduino'da kaydedilir;
Arduino listesinin yönetimi
Arduino listesinin görüntülenmesi şu anda [ConfigFragment] sınıfındaki iki yöntemle yönetilmektedir:
- [clearArduinos]: boş bir liste görüntüler;
- [showArduinos]: sunucu tarafından gönderilen listeyi görüntüler;
Bu iki yöntem şu şekilde geliştirilmektedir:
// Arduino listesi sıfırlanır
private void clearArduinos() {
// boş bir liste görüntülenir
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, new ArrayList<CheckedArduino>(), false);
listArduinos.setAdapter(adapter);
}
// Arduino listesini görüntüleme
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// Arduino'ları görüntüleme
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, checkedArduinos, false);
listArduinos.setAdapter(adapter);
}
Görev: Bu değişiklikleri yapın ve yeni uygulamayı test edin.

5.6.10.5. Oturum
Oturum, parçalar ve etkinlik tarafından paylaşılan bilgileri koyduğumuz yerdir. Tüm parçaların bağlı Arduino'ların listesini görüntülemesi gerekir. Dolayısıyla oturumun ilk sürümü şu şekilde olacaktır:
package client.android.architecture.custom;
import client.android.activity.CheckedArduino;
import client.android.architecture.core.AbstractSession;
import java.util.ArrayList;
import java.util.List;
public class Session extends AbstractSession {
// parçalar arasında ve parçalar ile etkinlik arasında paylaşılacak veriler
// jSON'te serileştirilemeyen öğeler @JsonIgnore anotasyonuna sahip olmalıdır
// jSON formatında serileştirme/deserileştirme için gerekli getter ve setter yöntemlerini unutmayın
// Arduino listesi
private List<CheckedArduino> checkedArduinos = new ArrayList<>();
// getter ve setter'lar
...
}
Görev: Yukarıdaki [Session] sınıfını oluşturun.
Bu oturumun oluşturulması, daha önce yazılmış kodu şu şekilde değiştirmemizi gerektirir:
// yanıtın görüntülenmesi
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// hata mı?
if (response.getStatus() != 0) {
// görüntüleme
showAlert(response.getMessages());
// iptal
doAnnuler();
// UI'ye geri dönüş
return;
}
// [CheckedArduino] listesi oluşturuluyor
List<CheckedArduino> checkedArduinos = new ArrayList<>();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// oturuma ekleniyor
session.setCheckedArduinos(checkedArduinos);
// görüntülenir
showArduinos(checkedArduinos);
// beklemeyi iptal ediyoruz
cancelWaitingTasks();
}
- 18. satır: önceki satırlarla oluşturulan Arduino listesi oturuma eklenir;
5.6.10.6. Parça durumunun yönetimi
Aygıt döndürüldüğünde, görünümün görsel bileşenleri (varsayılan olarak) görünümün tasarlandığı andaki durumlarında görüntülenir:
- [ListView], tasarımcının içine koyduğu öğeleri içerir;
- hata mesajı, tasarımcının belirlediği görünür veya görünmez durumdadır;
Tasarım aşamasındaki görsel bileşenlerin durumları, bir parçanın geri yüklenmesi sırasında uygun olabilir veya olmayabilir. Peki bu durumda durum nedir?
- [ListView], bağlı Arduino'ların listesini göstermelidir. Dolayısıyla, tasarım aşamasındaki [ListView]'in değeri kullanılamaz;
- Hata mesajındaki [TextView], kaydedildiği andaki görünür veya görünmez durumuna geri getirilmelidir. Tasarım aşamasındaki değeri bu iki duruma da uygun olmayabilir;
Bu nedenle, parça durumunu kaydederken bu iki bileşenin durumunu da kaydetmemiz gerekir:
- bağlı Arduino'ların listesi;
- web hizmeti / jSON'in URL girişi sırasında hata mesajının görünürlük durumu (görünür / gizli);
Oturumda bulunan Arduino'ların listesi otomatik olarak kaydedilecektir. Hata mesajının görünürlüğü ise aşağıdaki [ConfigFragmentState] sınıfında saklanacaktır:
![]() |
package client.android.fragments.state;
import client.android.architecture.custom.CoreState;
public class ConfigFragmentState extends CoreState {
// hata mesajı görünürlüğü
private boolean txtMsgErreurUrlServiceRestVisible;
// alıcı ve ayarlayıcılar
...
}
Ödev: Yukarıdaki [ConfigFragmentState] sınıfını oluşturun.
Parçaların durumlarını doğru şekilde geri yüklemek için, [getNumView] ve [saveFragment] yöntemlerinin değiştirilmesi gerekir. Örneğin, [BlinkFragment] parçasının yöntemi şu anda şöyledir:
@Override
public CoreState saveFragment() {
// parçayı kaydetmek gerekir
DummyFragmentState state=new DummyFragmentState();
// ...
return state;
// eğerkaydedilecek bir şey yoksa [return new CoreState();] komutunu çalıştırın ve [DummyFragmentState] sınıfını silin
}
@Override
protected int getNumView() {
// faaliyet tarafından yönetilen parçalar tablosuna parça numarasını geri yazmak gerekir (bkz. MainActivity)
return 0;
}
Herhangi bir işlem yapılmazsa, 6. satırda oluşturulan durum, [AbstractSession] sınıfındaki (aşağıdaki 5. satır) CoreState[] coreStates dizisinin 0. elemanına (13. satır) kaydedilecektir:
public class AbstractSession implements ISession {
...
// görünüm durumu
private CoreState[] coreStates = new CoreState[0];
...
Oysa bu değer, [MainActivity] sınıfında tanımlanan parçalar tablosunda (aşağıdaki 9. satır) [BlinkFragment] parça numarasına karşılık gelen elemana kaydedilmelidir:
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
...
@Override
protected AbstractFragment[] getFragments() {
return new AbstractFragment[]{new ConfigFragment_(), new BlinkFragment_(), new PinReadFragment_(), new PinWriteFragment_(), new CommandsFragment_()};
}
Parça numaraları, [IMainActivity] arayüzünde tanımlanmıştır:
public interface IMainActivity extends IDao {
...
// görünüm numaraları
int VUE_CONFIG = 0;
int VUE_BLINK = 1;
int VUE_PINREAD = 2;
int VUE_PINWRITE = 3;
int VUE_COMMANDS = 4;
}
Sonuç olarak, [BlinkFragment] fragmanının durumu şu şekilde yazıldığında doğru şekilde yönetilecektir:
@Override
public CoreState saveFragment() {
// parçayı kaydetmek gerekir
DummyFragmentState state=new DummyFragmentState();
// ...
return state;
// eğerkaydedilecek bir şey yoksa [return new CoreState();] komutunu çalıştırın ve [DummyFragmentState] sınıfını silin
}
@Override
protected int getNumView() {
// aktivite tarafından yönetilen parçalar tablosuna parça numarasını geri yazmak gerekir (bkz. MainActivity)
return IMainActivity.VUE_BLINK;
}
- 14. satır: Etkinlik tarafından yönetilen parçalar tablosunda [BlinkFragment] parça numarası döndürülür;
Ayrıca, parça durumlarının üst sınıfı olan [CoreState] sınıfı şu anda şu şekildedir (bkz. paragraf 5.6.7.2):
package client.android.architecture.custom;
import client.android.architecture.core.MenuItemState;
import client.android.fragments.state.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
@JsonSubTypes.Type(value = ConfigFragmentState.class),
@JsonSubTypes.Type(value = BlinkFragmentState.class),
@JsonSubTypes.Type(value = PinReadFragmentState.class),
@JsonSubTypes.Type(value = PinWriteFragmentState.class),
@JsonSubTypes.Type(value = CommandsFragmentState.class)}
)
public class CoreState {
// fragman ziyaret edilmiş mi, edilmemiş mi
protected boolean hasBeenVisited = false;
// parçanın olası menüsünün durumu
protected MenuItemState[] menuOptionsState;
// getter ve setter'lar
....
}
- 12-16. satırlar: [DummyFragmentState] sınıfı, [CoreState] sınıfının alt sınıflar listesinde yer almamaktadır. Oysa [BlinkFragment] sınıfının [saveFragment] yöntemi şu anda [ DummyFragmentState] türünde bir değer döndürmektedir. Durum bu şekilde bırakılırsa, oturumun serileştirilmesi/serileştirilmesinden sonra geri yüklenmesi başarısız olacak ve oturum geri yüklenemeyecek, bu da uygulamanın çökmesine yol açacaktır;
[BlinkFragment] parçasındaki [saveFragment] yöntemi şu şekilde yeniden yazılmalıdır:
@Override
public CoreState saveFragment() {
// parçanın kaydedilmesi gerekiyor
BlinkFragmentState state=new BlinkFragmentState();
// ...
return state;
// eğerkaydedilecek bir şey yoksa [return new CoreState();] komutunu çalıştırın ve [DummyFragmentState] sınıfını silin
}
Yapılması gereken: Her bir parçada, [getNumView] yöntemini parça numarasını döndürecek şekilde ve [saveFragment] yöntemini parça durum sınıfının bir örneğini döndürecek şekilde değiştirin (yukarıdaki gibi).
5.6.10.7. Parça yaşam döngüsünün yönetimi
Burada, [ConfigFragment] fragmanının yaşam döngüsüyle ilgileniyoruz, özellikle de şu dört yöntemle:
- [saveFragment]: Parçanın durumunu, daha sonra geri yüklenebilmesi için kaydetmelidir;
- [initFragment]: Gerekirse fragmanın belirli alanlarını başlatmalıdır. Bu yöntem, uygulamanın başlatılması sırasında ve cihazın her döndürülüşünde çağrılır. Daha kesin olarak, fragman yukarıdaki iki olaydan birinin ardından görünür hale geldiğinde çağrılır;
- [initView]: Gerektiğinde görünümün belirli bileşenlerini başlatmalıdır. Bu yöntem, [initFragment] çağrıldığında ve fragman belirli bir anda görüntülenen fragmanın bitişiğinden çıktığı için görünümün yeniden oluşturulması gerektiğinde çağrılır. Daha önce olduğu gibi, bu yöntem, bu olaylardan birinin ardından parça görünür hale geldiğinde çağrılır;
- [updateOnRestore]: Cihazın döndürüldüğü durumlarda ve ayrıca gezinme yapıldığında, önceki iki yöntemin ardından yürütülür. Görevi, parçanın önceki durumunu geri yüklemektir;
Bu yöntemler şunlardır:
// Arduino listesi adaptörü
private ListArduinosAdapter adapterListArduinos;
...
// parça yaşam döngüsü yönetimi -------------------------------------
@Override
public CoreState saveFragment() {
ConfigFragmentState state = new ConfigFragmentState();
state.setTxtMsgErreurUrlServiceRestVisible(txtMsgErreurUrlServiceRest.getVisibility() == View.VISIBLE);
return state;
}
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adaptörü
adapterListArduinos = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), false);
}
@Override
protected void initView(CoreState previousState) {
// listview / adaptör bağlantısı
listArduinos.setAdapter(adapterListArduinos);
// İlk ziyaret mi?
if (previousState == null) {
// ListView boş - [initFragment] tarafından oluşturuldu
// gizlenmiş hata mesajı
txtMsgErreurUrlServiceRest.setVisibility(View.INVISIBLE);
} else {
// hata mesajının görünürlüğü geri yükleniyor
ConfigFragmentState state = (ConfigFragmentState) previousState;
txtMsgErreurUrlServiceRest.setVisibility(state.isTxtMsgErreurUrlServiceRestVisible() ? View.VISIBLE : View.INVISIBLE);
}
}
@Override
protected void updateOnSubmit(CoreState previousState) {
}
@Override
protected void updateOnRestore(CoreState previousState) {
}
@Override
protected void notifyEndOfUpdates() {
// düğmeler
initButtons();
}
- 2. satır: Arduino'ların ListView adaptörü. Farklı yöntemlerde kullanıldığı için küresel bir değişkendir;
- satır 7-12: [saveFragment] yöntemi, TextView ve txtMsgErreurUrlServiceRestVisible'in görünürlük bilgilerini (satır 10) bir [ConfigFragmentState] türüne kaydeder;
- satır 14-19: [initFragment] yöntemi, 2. satırdaki adaptörü oturumda bulunan Arduino listesiyle başlatır (satır 17). [initFragment]'in rolünün, parçanın alanlarını başlatmak olduğunu hatırlatırız. Burada bu başlatma işlemi her durumda yapılmalıdır; ilk ziyaret (previousState==null) olsun ya da olmasın;
- 17. satır: Adaptörün [session.getCheckedArduinos] veri kaynağına bağlı olduğu görülüyor. Bu kaynağın değeri null olmamalıdır. Bu nedenle, [session.checkedArduinos] alanı oturumda boş bir listeyle başlatılır:
// Arduino listesi
private List<CheckedArduino> checkedArduinos = new ArrayList<>();
- 21-35. satırlar: [initView] yöntemi, görsel arayüzün belirli bileşenlerini, özellikle de cihazın döndürülmesi sırasında değeri korunmayan bileşenleri başlatmakla görevlidir;
- 24. satır: Arduino'ların ListView yöntemi, 2. hattaki adaptörle ilişkilendirilir;
- satır 28-32: İlk ziyaret, diğer ziyaretlerden ayrılır;
- 29. satır: İlk ziyaret sırasında boş bir [ListView] görüntülenmelidir. İlk ziyaret sırasında [ListView] adaptörü boş bir listeyle eşleştirildiğinden (17. satır) bu durum gerçekleşmiştir;
- satır 31: hata mesajı gizlenmiştir;
- satır 32-36: ilk ziyaret olmadığı durum;
- [ListView], 24. satırdan itibaren zaten doğru durumdadır. Yapılacak başka bir şey yoktur;
- satır 34-35: hata mesajı, parçanın son kaydedildiği andaki durumuna geri getirilir;
- satır 31-36: [updateOnRestore] yöntemi, parçayı başlangıç durumuna geri getirmelidir. [updateOnRestore] yöntemine iki şekilde ulaşılır:
- ya cihazın döndürülmesi nedeniyle. Bu durumda, yapılması gereken tüm başlatma işlemleri [initView] yönteminde gerçekleştirilmiştir;
- ya da bir sekmeden [Config] sekmesine geçildiğinde. [Config] parçası, terk edildiğinden beri görüntülenen parçaların komşuluğundan çıkmışsa, [initView] yöntemi yürütülmüş demektir ve parça zaten istenen durumdadır. [Config] parçası, ondan ayrıldığımızdan beri görüntülenen parçaların komşuluğundan çıkmamışsa, görsel bileşenlerinin durumu değişmemiştir ve yapılacak bir şey yoktur;
[updateOnRestore] yönteminin yapacak bir işi olmadığı görülüyor. Bazen durum böyledir, bazen değildir. Aradaki fark, [updateOnSubmit] yönteminden kaynaklanmaktadır: Eğer bu yöntem, [initView]'te yapılan bazı başlatma işlemlerini gereksiz kılan bir işlem yapıyorsa, o zaman bu başlatma işlemleri [updateOnRestore] yönteminde yapılmalıdır. V1, V2, V3 değerlerine sahip üç seçenekli bir radyo düğmesi örneğini ele alalım. Belki de [SUBMIT] eylemine bağlı bir gezinme durumunda, işaretli radyo düğmesi her zaman V1 değerine sahip olan olmalıdır. Bu durumda, [initView] yönteminde radyo düğmesinin değerini geri yüklemek gereksizdir; çünkü [SUBMIT] durumunda bu değer, [updateOnSubmit] yöntemi tarafından verilen değerle değiştirilecektir. Bu nedenle, bazen gereksiz bir işlem yapılmasını önlemek için bu geri yükleme işlemini [updateOnRestore] yöntemine taşımak daha uygun olur.
- 48-52. satırlar: [notifyEndOfUpdates] yöntemi, önceki tüm yöntemlerin ardından yürütülür;
- 51. satır: Düğmeler başlangıç durumlarına getirilir: [Rafraîchir] düğmesi gösterilir, [Annuler] düğmesi gizlenir:
Görev: Yukarıdaki kodu [ConfigFragment]'e ekleyin ve ardından uygulamayı çalıştırın. Cihazı döndürdüğünüzde, [Config] sekmesinin durumunu (hata mesajı, Arduino listesi) koruduğunu gözlemleyin. [config] sekmesinden [Commands] sekmesine, oradan da [Config] sekmesine basit bir sekme geçişi yaptığınızda da durumun aynı olduğunu kontrol edin. Bu son durumda, [IMainActivity]'te 1'lik parça komşuluğunu koruduysanız, o zaman [ConfigFragment] fragmanının görünümü, [Commands] sekmesine geçildiğinde yok edilir ve [Config] sekmesine geri dönüldüğünde yeniden oluşturulur. Testler sırasında günlükleri inceleyin.
5.6.10.8. Kodun iyileştirilmesi
[ConfigFragment] parçasının kodu iyileştirilebilir. Örneğin, şunu yazmıştık:
// Arduino listesi adaptörü
private ListArduinosAdapter adapterListArduinos;
...
// Arduino listesinin görüntülenmesi
private void showArduinos(List<CheckedArduino> checkedArduinos) {
// Arduino'lar görüntüleniyor
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, checkedArduinos, false);
listArduinos.setAdapter(adapter);
}
// Arduino listesini sıfırla
private void clearArduinos() {
// boş listeyi göster
ListArduinosAdapter adapter = new ListArduinosAdapter(getActivity(), R.layout.listarduinos_item, new ArrayList<CheckedArduino>(), false);
listArduinos.setAdapter(adapter);
}
- Görüldüğü gibi, 9. ve 16. satırlarda, 2. satırdaki alanla bağlantısı olmayan bir yerel değişken kullanılmış, oysa işlenmek istenen aslında aynı varlıktır;
Kodu şu şekilde değiştiriyoruz:
// Arduino listesi adaptörü
private ListArduinosAdapter adapterListArduinos;
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
...
}
private void getArduinosInBackground() {
...
// tüketilir
consumeArduinosResponse(response);
}
// yanıtın görüntülenmesi
private void consumeArduinosResponse(Response<List<Arduino>> response) {
// hata mı?
if (response.getStatus() != 0) {
// görüntüleme
showAlert(response.getMessages());
// iptal
doAnnuler();
// kullanıcı arayüzüne geri dön
return;
}
// [CheckedArduino] listesi oluşturuluyor
List<CheckedArduino> checkedArduinos = session.getCheckedArduinos();
checkedArduinos.clear();
for (Arduino arduino : response.getBody()) {
checkedArduinos.add(new CheckedArduino(arduino, false));
}
// görüntüleniyor
adapterListArduinos.notifyDataSetChanged();
// beklemeyi iptal ediyoruz
cancelWaitingTasks();
}
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adaptörü
adapterListArduinos = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), false);
}
@Override
protected void initView(CoreState previousState) {
// listview / adaptör bağlantısı
listArduinos.setAdapter(adapterListArduinos);
...
}
- 5. satırdaki yöntem yürütüldüğünde, parçanın yaşam döngüsü tamamlanmış olur. Dolayısıyla:
- 2. satırdaki adaptör, veri kaynağına (41. satır) eşleştirilmiştir;
- bağlı Arduino'ların [ListView]'i bu adaptöre bağlanmıştır (48. satır);
[ListView]'in görüntüsünü değiştirmek istediğimizde iki şey yapmamız gerekir:
- [session.checkedArduinos] veri kaynağının içeriğini değiştirmek;
- [adapterListArduinos.notifyDataSetChanged()] komutuyla bu değişikliği adaptöre bildirmek;
Burada söz konusu olan, veri kaynağının kendisini değil, veri kaynağının içeriğini değiştirmektir. Veri kaynağının kendisi değiştirilirse, [adapterListArduinos.notifyDataSetChanged()] işlemi eski veri kaynağını görüntülemeye devam edecektir. Bu durumda, adaptörü yeni veri kaynağıyla ilişkilendirmek gerekir.
Kod şu şekildedir:
- 27. satır: veri kaynağını alıyoruz;
- 28. satır: veritabanını boşaltıyoruz. Bu nedenle, [clearArduinos] yöntemini kaldırdık;
- 29-31. satırlar: Boşalan bu listeye yeni öğeler ekliyoruz;
- 33. satır: adaptöre kendini yenilemesini söylüyoruz. Bu, ilişkili [ListView]'in görüntüsünü yenileyecektir;
Ödev: Bu değişiklikleri yapın ve uygulamanızın hâlâ çalıştığını kontrol edin.
5.6.11. Görünümler Arası İletişim
Görünümler arası iletişimi kontrol etmek için, [Config] görünümü tarafından elde edilen Arduino listesini diğer tüm görünümlerde görüntüleyeceğiz. [blink.xml] görünümüyle başlayalım. Daha önce hiçbir şey göstermeyen bu görünüm, artık bağlı Arduino'ların listesini gösterecektir:

![]() |
[blink.xml] görünümünün XML kodu şu şekilde olacaktır:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txt_arduinos"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginTop="150dp"
android:text="@string/titre_list_arduinos"
android:textColor="@color/blue"
android:textSize="20sp" />
<ListView
android:id="@+id/ListViewArduinos"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_arduinos"
android:layout_marginTop="30dp"
android:background="@color/wheat">
</ListView>
</RelativeLayout>
</ScrollView>
Bu kod, [config.xml] görünümünden doğrudan alınmıştır. Sadece 19. satırın üst kenar boşluğu değiştirilmiştir.
Yapılacak iş: Bu kodu [commands.xml, pinread.xml, pinwrite.xml] görünümlerine kopyalayın.
[blink.xml] görünümüne ait [BlinkFragment] parça kodu da şu şekilde değişir:
![]() |
// görsel bileşenler
@ViewById(R.id.ListViewArduinos)
protected ListView listArduinos;
// Arduino listesi adaptörü
private ListArduinosAdapter adapterListArduinos;
...
// üst sınıf tarafından zorunlu kılınan yöntemler -------------------------------------------------------
...
@Override
protected void initFragment(CoreState previousState) {
// listArduinos adaptörü
adapterListArduinos = new ListArduinosAdapter(activity, R.layout.listarduinos_item, session.getCheckedArduinos(), true);
}
@Override
protected void initView(CoreState previousState) {
// ListView / adaptör bağlantısı
listArduinos.setAdapter(adapterListArduinos);
}
...
- 2-3. satırlar: bağlı Arduino'ların [ListView] bileşeni;
- 6. satır: bu [ListView]'in adaptörü;
- satır 12-23: [initFragment] ve [initView] yöntemlerinin kodu, [ConfigFragment] parçası için zaten kullanılmış olan koddur;
- 15. satır: Parça sıfırlanması gerektiğinde, 2. satırdaki adaptör, oturumda saklanan Arduino listesiyle eşleştirilerek sıfırlanır. [ListArduinosAdapter] yapıcısının son parametresi olan [true], her Arduino'nun yanında bir onay kutusu görmek istediğimiz anlamına gelir;
- 22. satır: Parçanın görünümü sıfırlanması gerektiğinde, bağlı Arduino'ların [ListView] parametresi 6. satırdaki adaptörle eşleştirilir;
Görev: Bu kodu diğer [CommandsFragment, PinReadFragment, PinWriteFragment] parçalarına kopyalayın. Uygulamayı çalıştırın ve artık her sekmede bağlı Arduino'ların listesinin görüntülendiğini kontrol edin. Ayrıca, bir sekmede Arduino'ları işaretleyip başka bir sekmeye geçtiğinizde, bunların yeni sekmede de işaretli olarak kaldığını kontrol edin.
Not: Arduino'ların işaretli kalmasının açıklaması şöyledir. [ListArduinosAdapter] sınıfı 5.6.10.4. paragrafında tanıtılmıştır. Onay kutusuyla ilgili kod şöyledir:
// mevcut Arduino
final CheckedArduino arduino = arduinos.get(position);
...
// CheckBox her zaman görünür değildir
CheckBox ck = (CheckBox) row.findViewById(R.id.checkBoxArduino);
ck.setVisibility(selectable ? View.VISIBLE : View.INVISIBLE);
if (selectable) {
// değer atanıyor
ck.setChecked(arduino.isChecked());
// tıklama işleniyor
ck.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
arduino.setChecked(isChecked);
}
});
}
- 11-15. satırlar: X sekmesinde bir onay kutusu işaretlendiğinde, 2. satırdaki Arduino'nun [checked] özelliği true olarak değiştirilir (14. satır);
- Y sekmesine geçildiğinde, bu sekmedeki Arduino'ların [ListView] değeri görüntülenir. 9. satırda, 2. satırdaki Arduino'nun [checked] özelliğinin true olarak değiştirilmesi durumunda, 5. satırdaki [ck] kutucuğunun işaretleneceği görülür;
5.6.12. [DAO] katmanı
![]() |
Not: Bu bölüm için, [exemple-16B] projesindeki [DAO] katmanının uygulamasını tekrar gözden geçirin (bkz. 2.8.3. paragraf).
Şu ana kadar bağlı Arduino'ların listesini manuel olarak oluşturduk. Şimdi bu listeyi /jSON web sunucusundan isteyeceğiz. Bunun için [DAO] katmanını oluşturacağız:
![]() |
5.6.12.1. IDao arayüzü
[DAO] katmanının [IDao] arayüzü şu şekilde olacaktır:
package client.android.dao.service;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import rx.Observable;
import java.util.List;
public interface IDao {
// Web hizmetinin URL'si
void setUrlServiceWebJson(String url);
// kullanıcı
void setUser(String user, String mdp);
// istemci zaman aşımı
void setTimeout(int timeout);
// temel kimlik doğrulama
void setBasicAuthentification(boolean isBasicAuthentificationNeeded);
// hata ayıklama modu
void setDebugMode(boolean isDebugEnabled);
// İstek öncesi istemci bekleme süresi (milisaniye)
void setDelay(int delay);
// özel ----------------------------------------
// Arduino listesi
Observable<Response<List<Arduino>>> getArduinos();
}
- 11-26. satırlar: Bu satırlar, [client-android-skel] model projesinin [IDao] arayüzünde zaten mevcuttur;
- 30. satır: [getArduinos] yöntemi, bağlı Arduino'ların listesini Observable<[Response<List<Arduino>>>] türünde bir gözlemlenebilir olarak elde etmeyi sağlar;
[Response<T>]'in, sunucu tarafından jSON dizesi biçiminde gönderilen tüm yanıtların türü olduğunu hatırlatırız:
package client.android.dao.entities;
import java.util.List;
public class Response<T> {
// ----------------- özellikler
// işlemin durumu
private int status;
// olası hata mesajları
private List<String> messages;
// yanıt gövdesi
private T body;
// yapıcılar
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// alıcı ve ayarlayıcılar
...
}
5.6.12.2. [WebClient] arayüzü
![]() |
[WebClient] arayüzü, AA kütüphanesi tarafından bir uygulaması sağlanan bir arayüzdür. Bu arayüz şu şekilde olacaktır:
package client.android.dao.service;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import org.androidannotations.rest.spring.annotations.Get;
import org.androidannotations.rest.spring.annotations.Path;
import org.androidannotations.rest.spring.annotations.Rest;
import org.androidannotations.rest.spring.api.RestClientRootUrl;
import org.androidannotations.rest.spring.api.RestClientSupport;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Rest(converters = {MappingJackson2HttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {
// RestTemplate
void setRestTemplate(RestTemplate restTemplate);
// özel --------------------------------------
// Arduino listesi
@Get("/arduinos")
Response<List<Arduino>> getArduinos();
}
- 15-19. satırlar: Bu satırlar, [client-android-skel] örnek projesinin [WebClient] arayüzünde varsayılan olarak bulunur;
- 23. satır: URL sunucusu, GET işlemiyle Arduino listesini almayı sağlar. Bu URL'in, 16. satırdaki [RestClientRootUrl] kök URL'e göre ölçüldüğünü hatırlatırız;
- 24. satır: sunucu, [Response<List<Arduino>>] türünde bir jSON dizesini geri gönderir. Bu jSON dizisi, 15. satırdaki jSON [MappingJackson2HttpMessageConverter] dönüştürücüsü sayesinde otomatik olarak [Response<List<Arduino>>] türüne deserialize edilir;
5.6.12.3. [Dao] sınıfı
[Dao] sınıfı, [IDao] arayüzünü şu şekilde uygular:
package client.android.dao.service;
import android.util.Log;
import client.android.dao.entities.Arduino;
import client.android.dao.entities.Response;
import org.androidannotations.annotations.AfterInject;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
import org.androidannotations.rest.spring.annotations.RestService;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import java.util.ArrayList;
import java.util.List;
@EBean(scope = EBean.Scope.Singleton)
public class Dao extends AbstractDao implements IDao {
// web hizmeti istemcisi
@RestService
protected WebClient webClient;
// güvenlik
@Bean
protected MyAuthInterceptor authInterceptor;
// RestTemplate
private RestTemplate restTemplate;
// RestTemplate fabrikası
private SimpleClientHttpRequestFactory factory;
@AfterInject
public void afterInject() {
// günlük
Log.d(className, "afterInject");
// restTemplate üretiliyor
factory = new SimpleClientHttpRequestFactory();
restTemplate = new RestTemplate(factory);
// jSON dönüştürücüsünü sabitliyoruz
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// web istemcisinin restTemplate'i belirlenir
webClient.setRestTemplate(restTemplate);
}
@Override
public void setUrlServiceWebJson(String url) {
// web hizmetinin URL'i belirlenir
webClient.setRootUrl(url);
}
@Override
public void setUser(String user, String mdp) {
// kullanıcı, yakalayıcıya kaydedilir
authInterceptor.setUser(user, mdp);
}
@Override
public void setTimeout(int timeout) {
if (isDebugEnabled) {
Log.d(className, String.format("setTimeout thread=%s, timeout=%s", Thread.currentThread().getName(), timeout));
}
// fabrika yapılandırması
factory.setReadTimeout(timeout);
factory.setConnectTimeout(timeout);
}
@Override
public void setBasicAuthentification(boolean isBasicAuthentificationNeeded) {
if (isDebugEnabled) {
Log.d(className, String.format("setBasicAuthentification thread=%s, isBasicAuthentificationNeeded=%s", Thread.currentThread().getName(), isBasicAuthentificationNeeded));
}
// kimlik doğrulama yakalayıcısı mı?
if (isBasicAuthentificationNeeded) {
// kimlik doğrulama ara bileşeni ekleniyor
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(authInterceptor);
restTemplate.setInterceptors(interceptors);
}
}
// özel yöntemler -------------------------------------------------
private void log(String message) {
if (isDebugEnabled) {
Log.d(className, message);
}
}
// IDao'e özgü uygulama -----------------------------------------------
@Override
public Observable<Response<List<Arduino>>> getArduinos() {
// web istemcisi çalıştırma
return getResponse(new IRequest<Response<List<Arduino>>>() {
@Override
public Response<List<Arduino>> getResponse() {
return webClient.getArduinos();
}
});
}
}
- 19-87. satırlar: Bu satırlar, [client-android-skel] projesindeki [Dao] sınıfının temelini oluşturur;
- 91-100. satırlar: [getArduinos] yönteminin uygulaması;
- 94. satır: üst sınıftaki [getResponse] yöntemini çağırır. Bu yöntemin tek parametresi, [IRequest<T>] arayüzünün bir örneğidir;
- 95-99. satırlar: [IRequest<T>] arayüzünün tek yöntemi, [T getResponse()] yöntemidir;
- 94. satır: [IRequest<T>]'in T türü, 92. satırdaki yöntemin sonucu olan Observable<T>'in T türü olmalıdır; dolayısıyla burada bir [Response<List<Arduino>>] türü;
- 97. satır: [IRequest.getResponse()] yöntemi, daha önce tanıttığımız [webClient.getArduinos()] yöntemine işi devrediyor. 24. satırda tanımlanan [webClient], AA kütüphanesi tarafından örneklenir ve daha önce tanıttığımız [WebClient] arayüzünün bir örneğidir;
5.6.13. [MainActivity] etkinliği
![]() |
[MainActivity] etkinliğini 5.6.8. paragrafında daha önce tanıtmıştık. Bu aktivite, [AbstractActivity] sınıfını genişletir ve bu nedenle, kendisi de [IDao] arayüzünü genişleten [IMainActivity] arayüzünü uygular. [IDao] arayüzüne her yöntem eklendiğinde, bu yöntem [MainActivity] sınıfında uygulanmalıdır. [IDao] arayüzüne eklenen [IDao.getArduinos] yöntemi, [MainActivity] sınıfında şu şekilde uygulanacaktır:
...
@EActivity
@OptionsMenu(R.menu.menu_main)
public class MainActivity extends AbstractActivity {
// [DAO] katmanı
@Bean(Dao.class)
protected IDao dao;
// oturum
private Session session;
...
// IDao uygulaması -----------------------------------------
@Override
public Observable<Response<List<Arduino>>> getArduinos() {
return dao.getArduinos();
}
}
- 15-18. satırlar: [getArduinos] yöntemi, az önce tanıtılan ve 8. satırda referansı bulunan [Dao] sınıfına işi devrederek uygulanır;
5.6.14. [ConfigFragment] parçası yeniden inceleniyor
[ConfigFragment] sınıfında, [Rafraîchir] düğmesine tıklandığında yürütülen kod şimdilik şöyledir:
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
...
// Arduino listesi arka planda isteniyor
getArduinosInBackground();
}
private void getArduinosInBackground() {
// hayali bir Arduino listesi oluşturuluyor
List<Arduino> arduinos = new ArrayList<>();
for (int i = 0; i < 20; i++) {
arduinos.add(new Arduino("id" + i, "desc" + i, "mac" + i, "ip" + i, i));
}
// sunucudan gelen yanıt simüle ediliyor
Response<List<Arduino>> response = new Response<>();
response.setBody(arduinos);
// bu yanıtı işleniyor
consumeArduinosResponse(response);
}
// yanıtın görüntülenmesi
private void consumeArduinosResponse(Response<List<Arduino>> response) {
...
}
[Response<List<Arduino>>] türünde sabit bir yanıt üreten 10-16. satırları yeniden yazmamız gerekiyor. Artık bu listeyi, etkinlik aracılığıyla [DAO] katmanından talep etmeliyiz. Kod şu şekilde olur:
@Click(R.id.btn_Rafraichir)
protected void doRafraichir() {
// girişler kontrol ediliyor
if (!pageValid()) {
return;
}
// girişi kaydediliyor
mainActivity.setUrlServiceWebJson(urlServiceRest);
// bekleme hazırlığı yapılıyor
beginWaiting(1);
// asenkron görevi yürütülüyor
executeInBackground(mainActivity.getArduinos(), new Action1<Response<List<Arduino>>>() {
@Override
public void call(Response<List<Arduino>> response) {
// yanıt işleniyor
consumeArduinosResponse(response);
}
});
}
- 8. satır: Kullanıcı tarafından girilen web hizmetinin kök URL / jSON, aktivite aracılığıyla [DAO] katmanına aktarılır. Bu, [WebClient] arayüzünün kökü olan URL olacaktır (bkz. paragraf 5.6.12.2);
- 10. satır: üst sınıfa, asenkron bir görev başlatılacağı bildirilir;
- 12-19. satırlar: sunucuya bağlı Arduino'ların listesini getirecek asenkron görevin başlatılması;
- 12. satır: üst sınıfın [executeInBackground] yönteminin çağrılması. Bu yöntem iki parametre bekler:
- 12. satır: gözlemlenecek işlem. Bu işlem burada [mainActivity.getArduinos()] yöntemi tarafından sağlanır;
- satır 12-19: [Action1<T>] arayüzünün bir örneği; burada T türü, işlem tarafından sağlanan türdür (bu örnekte [Response<List<Arduino>>] türü);
- satır 14-18: asenkron görev, [Response<List<Arduino>>] türündeki sonucunu döndürdüğünde çağrılan yöntem;
- 17. satır: Alınan yanıt, daha önce yazılmış olan [consumeArduinosResponse] yöntemine aktarılır;
Ödev: Sunucuyu 5.4. paragrafında belirtildiği şekilde başlatın. Sunucunun çalıştırıldığı PC cihazına bir veya daha fazla Arduino bağlayın. Ardından Android istemcisini başlatın ve bağlı Arduino'ların listesini alabildiğinizi kontrol edin. Günlükleri inceleyin.

- [1]'te belirtilen URL adresini girin. Bu, sunucunuzun IP adreslerinden biridir;
- [2] düğmesine tıklayın;
- [3]'te bağlı Arduino'ların listesini görmelisiniz;
Bu listenin diğer sekmelerde de göründüğünü kontrol edin.
5.7. Yapılması gerekenler
Az önce [Config] görünümü için yaptığınız gibi, uygulamanın diğer dört görünümünü de sırasıyla oluşturun ve test edin: [Blink], [PinRead], [PinWrite] ve [Commands].
Oluşturulacak görünümler 5.5. paragrafında sunulmuştur.
Her bir görünüm için şunları yapmanız gerekir:
- XML görünümünü çizmek (bkz. paragraf 5.6.9);
- ilgili parçayı oluşturmak (bkz. paragraf 5.6.10);
- [WebClient] arayüzüne bir yöntem eklemek (bkz. paragraf 5.6.12.2);
- [IDao] arayüzüne bir yöntem eklemek (bkz. paragraf 5.6.12.2);
- [Dao] sınıfına bir yöntem eklemek (bkz. paragraf 5.6.12.3);
- [MainActivity] aktivitesine bir yöntem eklemek (bkz. paragraf 5.6.13);
- parçanın olay işleyicilerini yazın (bkz. paragraf 5.6.14);
- test etmek ve günlükleri incelemek;
Not 1: Örnek olarak dersin [Exemple-16B] projesi kullanılmalıdır (bkz. paragraf 2.8.3).
Not 2: Sorgulanacak URL'ler ve bunların yanıt türleri 5.4.2. paragrafında sunulmuştur.
Not 3:
[CommandsFragment] sınıfı, bir veya daha fazla Arduino tarafından yürütülecek tek bir komut içeren bir liste gönderir. Bu komut, aşağıdaki [ArduinoCommand] sınıfında kapsüllenecektir:
package android.arduinos.dao;
import java.util.Map;
public class ArduinoCommand {
// veri
private String id;
private String ac;
private Map<String, Object> pa;
// yapıcılar
public ArduinoCommand() {
}
public ArduinoCommand(String id, String ac, Map<String, Object> pa) {
this.id = id;
this.ac = ac;
this.pa = pa;
}
// alıcı ve ayarlayıcılar
...
}
[WebClient] arayüzünde, bu tek komutlu listeyi yürütmek için kullanılacak yöntem şöyledir:
// komutların gönderilmesi JSON
@Post("/arduinos/commands/{idArduino}")
Response<List<ArduinoResponse>> sendCommands(@Body List<ArduinoCommand> commands, @Path String idArduino);
- 2. satır: URL, HTTP POST emriyle talep edilir;
- 3. satır: gönderilen değerin [@Body] notu bulunmalıdır;
Not 4: Bu çalışmayı şu şekilde yapmanız tavsiye edilir:
- ancak mevcut görünüm oluşturulup test edildikten sonra bir sonraki görünüme geçin;
- görünümlerin durumunu, normal koşullarda işlevsel bir uygulama elde edildikten sonra yönetin. Ardından her görünüm için, cihazı farklı görünüm durumlarına getirerek kaybolan bilgileri not edin. Kaydedilmesi ve daha sonra geri yüklenmesi gereken bilgiler bunlardır. Ardından gezinmeyi kontrol edin: bir sekmeden çıkıp daha sonra geri döndüğünüzde, sekmeyi bıraktığınız durumda bulmalısınız;


















































