lunes, 9 de mayo de 2016

ANDROID NFC CON FIREBASE Y RASPBERRY PI



ANDROID NFC CON FIREBASE Y RASPBERRY PI









Descripción del proyecto 

Cuando el celular se acerca al tag NFC, lee la información (previamente guardada en este Tag), a través de NDEF  (NFC Data Exchange Format) y  con  Java (Android),  se compara el  texto obtenido del tag, para luego comunicarse con API de Firebase, 
Ejemplo, si lo que se leyó del tag1 es “sala”  entonces cambia el estado en firabese en la URL determinada.
Paralelo a esto, en Raspberry Pi (Python), se está accediendo continuamente a la misma URL de Firebase, lo que permite obtener el valor de los estados de (“sala” y “habitación”) y posteriormente controlar el  GPIO dependiendo el valor de estos estados.


ALGO DE TEORÍA 


Tecnología NFC

Near field communication (NFCcomunicación de campo cercano en español) es una tecnología de comunicación inalámbrica, de corto alcance y alta frecuencia que permite el intercambio de datos entre dispositivos (tomado de wikipedia)
Para más información sobre como se trabaja está tecnología en Android 
http://developer.android.com/intl/es/guide/topics/connectivity/nfc/index.html


NDEF

The NFC Data Exchange Format (Formato de intercambio de datos)  (NDEF) es un formato de datos normalizado que se puede utilizar para intercambiar información entre cualquier dispositivo NFC compatible y otro dispositivo NFC o etiqueta
En android



1. Instalar NFC Tools en Android.

Seleccionar escribir, y Añanidr un registro de texto, luego acerque la tarjeta al lado trasero del celular.
Si le arroja algún error, puede primero formatear la tarjeta o Tag NFC, y volver a intentar la escritura.

Para este ejemplo, hay que escribir   "sala"   (Sin comillas) y en otro tag, "habitacion", (tal como está sin acento)


  NFC Tools: captura de pantalla   NFC Tools: captura de pantalla



2. Aplicación Android

Les comparto el proyecto en Android Studio, ahi pueden encontrar el apk
El proyecto android se encarga de procesar la lectura del tag NFC, y de acuerdo a al texto obtenido se actualiza el estado en firebase, de una URL en especifico.


Solo voy a mostrar el main.java que es el archivo donde está la lógica.
El proyecto completo lo pueden descargar en GitHub 









package com.example.user.minfc;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

    public static final String MIME_TEXT_PLAIN = "text/plain";
    public static final String TAG = "NfcDemo";
    Firebase fire_sala, fire_habitacion;

    private TextView textLog = null;
    private TextView textSala, textHabitacion;
    private NfcAdapter mNfcAdapter = null;
    private Boolean ultimoEstadoSala, ultimoEstadoHabitacion;

    private ToggleButton toggleButtonSala, toggleButtonHabitacion;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fire_habitacion.setAndroidContext(this);
        fire_sala.setAndroidContext(this);

        fire_sala = new Firebase("https://nfcpi.firebaseio.com/luces/sala");
        fire_habitacion = new Firebase("https://nfcpi.firebaseio.com/luces/habitacion");
        fire_habitacion.setValue(false);
        fire_sala.setValue(false);


        textLog = (TextView) findViewById(R.id.msgLog);
        textSala = (TextView) findViewById(R.id.textSala);
        textHabitacion = (TextView) findViewById(R.id.textHabitacion);

        toggleButtonSala =  (ToggleButton) findViewById(R.id.tgsala);
        toggleButtonHabitacion =  (ToggleButton) findViewById(R.id.tghabitacion);

        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);

        toggleButtonSala.setEnabled(false);
        toggleButtonSala.setTextOff("Apagada");
        toggleButtonSala.setTextOn("Encendida");

        toggleButtonHabitacion.setEnabled(false);
        toggleButtonHabitacion.setTextOff("Apagada");
        toggleButtonHabitacion.setTextOn("Encendida");


        fire_sala.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                ultimoEstadoSala = ! (Boolean) dataSnapshot.getValue();
                toggleButtonSala.setChecked(!ultimoEstadoSala);
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });


        fire_habitacion.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                ultimoEstadoHabitacion = ! (Boolean) dataSnapshot.getValue();
                toggleButtonHabitacion.setChecked(!ultimoEstadoHabitacion);
            }

            @Override
            public void onCancelled(FirebaseError firebaseError) {

            }
        });

        if (mNfcAdapter == null) {
            Toast.makeText(this, "Este dispositivo no soporta NFC.", Toast.LENGTH_LONG).show();
            finish();
            return;
        }

        if (!mNfcAdapter.isEnabled()) {
            textLog.setText("NFC desactivdado");
        } else {
            textLog.setText("NFC activado");
        }

        handleIntent(getIntent());
    }


    @Override
    protected void onResume() {
        super.onResume();

        setupForegroundDispatch(this, mNfcAdapter);
    }

    @Override
    protected void onPause() {

        stopForegroundDispatch(this, mNfcAdapter);

        super.onPause();
    }


    @Override
    protected void onNewIntent(Intent intent) {
        handleIntent(intent);
    }

    private void handleIntent(Intent intent) {
        String action = intent.getAction();
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {

            String type = intent.getType();
            if (MIME_TEXT_PLAIN.equals(type)) {

                Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
                new NdefReaderTask().execute(tag);

            } else {
                Log.d(TAG, "Wrong mime type: " + type);
            }
        } else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {

            // In case we would still use the Tech Discovered Intent
            Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
            String[] techList = tag.getTechList();
            String searchedTech = Ndef.class.getName();

            for (String tech : techList) {
                if (searchedTech.equals(tech)) {
                    new NdefReaderTask().execute(tag);
                    break;
                }
            }
        }
    }



     /* @param activity The corresponding {@link Activity} requesting the foreground dispatch.
     * @param adapter The {@link NfcAdapter} used for the foreground dispatch.
     */
    public static void setupForegroundDispatch(final MainActivity activity, NfcAdapter adapter) {
        final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass());
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

        final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0);

        IntentFilter[] filters = new IntentFilter[1];
        String[][] techList = new String[][]{};

        // Notice that this is the same filter as in our manifest.
        filters[0] = new IntentFilter();
        filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
        filters[0].addCategory(Intent.CATEGORY_DEFAULT);
        try {
            filters[0].addDataType(MIME_TEXT_PLAIN);
        } catch (IntentFilter.MalformedMimeTypeException e) {
            throw new RuntimeException("Check your mime type.");
        }

        adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList);
    }



     /** @param activity The corresponding {@linkBaseActivity} requesting to stop the foreground dispatch.
     * @param adapter The {@link NfcAdapter} used for the foreground dispatch.
     */
    public static void stopForegroundDispatch(final MainActivity activity, NfcAdapter adapter) {
        adapter.disableForegroundDispatch(activity);
    }


     /** Background task for reading the data. Do not block the UI thread while reading.
     *
     * @author Ralf Wondratschek
     *
     */
    private class NdefReaderTask extends AsyncTask {

        @Override
        protected String doInBackground(Tag... params) {
            Tag tag = params[0];

            Ndef ndef = Ndef.get(tag);
            if (ndef == null) {
                // NDEF is not supported by this Tag.
                return null;
            }

            NdefMessage ndefMessage = ndef.getCachedNdefMessage();

            NdefRecord[] records = ndefMessage.getRecords();
            for (NdefRecord ndefRecord : records) {
                if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
                    try {
                        return readText(ndefRecord);
                    } catch (UnsupportedEncodingException e) {
                        Log.e(TAG, "Unsupported Encoding", e);
                    }
                }
            }

            return null;
        }


        private String readText(NdefRecord record) throws UnsupportedEncodingException {

         /** See NFC forum specification for "Text Record Type Definition" at 3.2.1
         *
         * http://www.nfc-forum.org/specs/
         *
         * bit_7 defines encoding
         * bit_6 reserved for future use, must be 0
         * bit_5..0 length of IANA language code
         */

            byte[] payload = record.getPayload();

            // Get the Text Encoding
            String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";

            // Get the Language Code
            int languageCodeLength = payload[0] & 0063;

            // String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
            // e.g. "en"

            // Get the Text
            return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
        }

        @Override
        protected void onPostExecute(String result) {
            if (result != null) {
                textLog.setText("Mensaje NDEF: " + result);

                if (result.equals("sala")){
                    Log.d("Sala: ", ultimoEstadoSala.toString());
                    fire_sala.setValue(ultimoEstadoSala);
                }

                if(result.equals("habitacion")){
                    Log.d("Habitación: ", ultimoEstadoHabitacion.toString());
                    fire_habitacion.setValue(ultimoEstadoHabitacion);
                }

            }
        }
    }
}







3. Instalando lo necesario en Raspbian


sudo apt-get update
sudo apt-get install python-dev
sudo apt-get install python-gpiozero
sudo wget https://bootstrap.pypa.io/get-pip.py
sudo python get-pip.py
sudo pip install requests==1.1.0
sudo pip install python-firebase

4. Código Python (Raspberry Pi)

Los archivo principales del proyecto son el main.py, que es el archivo principal y Conexion.py, es una clase que se encarga de comunicarse con Firebase


main.py


#!/usr/bin/python
# -*- coding: utf-8 -*-
#Autor: Jefferson Rivera
#Email: riverajefer@gmail.com

import sys
import signal
from gpiozero import LED
from clases.Conexion import Conexion

led_sala = LED(17)
led_habitacion = LED(27)

def procesa(value_sala, value_habitacion):
    if value_sala:
     led_sala.on()
     print "Encendido Sala"
    else:
     led_sala.off()
     print "Apagado Sala"

    if value_habitacion:
        led_habitacion.on()
        print "Encendido Habitación"
    else:
        led_habitacion.off()
        print "Apagado Habitación"        
    sys.stdout.flush()

try:
 print "Inicio"
 t = Conexion(procesa)
 t.daemon=True
 t.start()
 signal.pause()
except (KeyboardInterrupt, SystemExit):
 raise
 print "Salida"






clases/Conexion.py

Esta clase es la encargada de establecer la comunicación con Firebase, y enviar los datos apropiados al archivo main.py, para que este controle el GPIO



from firebase import firebase
import threading
import time

class Conexion(threading.Thread):

    def __init__(self, retorno):
        threading.Thread.__init__(self)
        self.retorno = retorno
        self.fire = firebase.FirebaseApplication('https://nfcpi.firebaseio.com/', None)
        self.ultimo_estado_sala = self.fire.get('/luces/sala', None)
        self.ultimo_estado_habitacion = self.fire.get('/luces/habitacion', None)
        self.retorno(self.ultimo_estado_sala, self.ultimo_estado_habitacion)

    def run(self):
  ES = []
  ES.append(self.ultimo_estado_sala)
  EH = []
  EH.append(self.ultimo_estado_habitacion)  
  i = 0
  
  while True:
   estado_actual_sala = self.fire.get('/luces/sala', None)
   ES.append(estado_actual_sala)
   estado_actual_habitacion = self.fire.get('/luces/habitacion', None)
   EH.append(estado_actual_habitacion)

   if ES[i] != ES[-1]:
    self.retorno(estado_actual_sala, estado_actual_habitacion)
   
   del ES[0]

   if EH[i] != EH[-1]:
    self.retorno(estado_actual_sala, estado_actual_habitacion)
   
   del EH[0]   
   i = i+i
   time.sleep(0.3)




5. Montar el siguiente circuito


6. Ejecutar el archivo main.py 






7. Usar su propia URL de Firebase

Aca estoy usando mi URL, pero recomiendo que mejor usen su propia URL de  Firebase, y no se olviden cambiar la referencia en en el main.java y en Conexion.py
Esos es todo lo que tienen que hacer, no es necesario crear las subrutas en firebase, eso lo hace automáticamente java









4 comentarios:

  1. hola si no es mucha molestia me podrias indicar como hacer para que cuando arranque la raspberry se ejecute el programa

    ResponderEliminar
  2. Hola
    No hablo muy espanol.
    sou brasileiro, onde achar raspberry pi barato em rivera?
    tenho um raspberry pi b e pretendo ter mais um

    ResponderEliminar
    Respuestas
    1. Hola (hi) Ronaldo.


      I do not understand the question



      Regards






      Cordialmente
      Jefferson Rivera

      Eliminar