Suscribirse a un topic MQTT desde Android

Para la conexión utilizaremos la librería Eclipse Paho Android Service.

Configuración Gradle

En el fichero `build.gradle` de nuestro proyecto Android será necesario añadir las siguientes dependencias:

    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
    implementation('org.eclipse.paho:org.eclipse.paho.android.service:1.1.1') {
        exclude group: 'com.android.support'
        exclude module: 'appcompat-v7'
        exclude module: 'support-v4'
    }

Servidor MQTT de pruebas (Mosquitto)

Dockerfile

FROM eclipse-mosquitto:1.4.12
ADD mosquitto.conf /mosquitto/config/mosquitto.conf

mosquito.conf

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

docker-compose.yml

version: '3'
services:
  mqttbroker:
    build: .
    image: test-mosquitto:0.0.1-SNAPSHOT
    container_name: test.mosquitto.container
    ports:
      - 9001:9001
      - 1883:1883

Para levantar el servicio MQTT será suficiente con ejecutar `docker-compose up -d` en la ruta donde se encontrasen los archivos anteriores.

Código Android

La aplicación de prueba consiste en una activity vacía que se suscribe al topic `test-topic` y muestra los mensajes recibidos en el log de Android.
AndroidManifest.xml
En el fichero AndroidManifest.xml será necesario añadir los siguiente permisos:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />

También habrá que añadir el servicio `MqttService` proporcionado por la librería:

<service android:name="org.eclipse.paho.android.service.MqttService" />

MainActivity.java

package org.eurecat.test.mqtt.android;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.UUID;

public class MainActivity extends AppCompatActivity implements MqttCallback, IMqttActionListener {

    private static final String SERVER_URI = "tcp://10.42.0.1:1883";
    private static final String TOPIC = "test-topic";
    private static final int QOS = 1;
    private static final String TAG = "MainActivity";

    private MqttAndroidClient mqttAndroidClient;

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

        String clientId = UUID.randomUUID().toString();
        Log.d(TAG, "onCreate: clientId: " + clientId);

        mqttAndroidClient = new MqttAndroidClient(getApplicationContext(), SERVER_URI, clientId);
        mqttAndroidClient.setCallback(this);

        MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
        mqttConnectOptions.setCleanSession(false);


        try {
            Log.d(TAG, "onCreate: Connecting to " + SERVER_URI);
            mqttAndroidClient.connect(mqttConnectOptions, null, this);
        } catch (MqttException ex){
            Log.e(TAG, "onCreate: ", ex);
        }
    }

    @Override
    public void onSuccess(IMqttToken asyncActionToken) {
        Log.d(TAG, "onSuccess: ");
        try {
            mqttAndroidClient.subscribe(TOPIC, QOS);
        } catch (Exception e) {
            Log.e(TAG, "Error subscribing to topic", e);
        }
    }

    @Override
    public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
        Log.e(TAG, "Failed to connect to: " + SERVER_URI, exception);
    }

    @Override
    public void connectionLost(Throwable cause) {
        Log.d(TAG, "connectionLost: ", cause);
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        Log.d(TAG, "Incoming message: " + new String(message.getPayload()));
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        Log.d(TAG, "deliveryComplete: ");
    }

}

Pruebas

En Ubuntu, es posible instalar el cliente ejecutando:

sudo apt install mosquitto-clients

Para enviar un mensaje al topic `test-topic` ejecutaremos:

mosquitto_pub -h localhost -t test-topic -m 'test mqtt message'

Esta es la salida del log de android:

10-26 13:44:05.679 24785 24785 D MainActivity: Incoming message: test mqtt message

Configurar DNS Server en router Jazztel ZTE F680

Recientemente he actualizado mi conexión a fibra óptica (Jazztel). Como tengo un equipo dedicado a servidor, me ha parecido interesante poder utilizar el router como servidor DNS. De esta forma es posible acceder al servidor por nombre lo que es mejor que andar tocando el fichero /etc/hosts en cada equipo que se conecta a la red.

El router es ZTE F680.

En primer lugar, en el apartado:

Network -> Lan -> DHCP Server

Marcaremos el check Assign DNS y en el campo DNS Server1 IP Address pondremos la IP del router. Pulsaremos el botón “submit” para confirmar.

zte-network-lan

A continuación, en el apartado:

Application -> DNS Service -> Domain Name

Configuraremos el nombre del dominio que queramos utilizar. En mi caso es “sourcerebels.com”.

zte-application-dns-domain

En el apartado:

Application -> DNS Service -> DNS

Configuraremos la ip del router (192.168.1.1) y el servidor DNS de Google (8.8.8.8):

zte-application-dns-dns

Para finalizar, en el apartado:

Application -> DNS Service -> Hosts

Definiremos los hosts que queramos así como la IP, en mi caso he definido el host con nombre “dev”.

zte-application-dns-hosts

De esta manera podemos ver los DNS que utiliza nuestro equipo (Mac o Linux):

cat /etc/resolv.conf

La salida de este comando en mi caso es:

 
#
# Mac OS X Notice
#
# This file is not used by the host name and address resolution
# or the DNS query routing mechanisms used by most processes on
# this Mac OS X system.
#
# This file is automatically generated.
#
domain sourcerebels.com
nameserver 192.168.1.1
nameserver 8.8.8.8

También comprobamos que la maquina es visible por nombre.

ping dev.sourcerebels.com

La salida de este comando en mi caso es:

PING dev.sourcerebels.com (192.168.1.2): 56 data bytes
64 bytes from 192.168.1.2: icmp_seq=0 ttl=64 time=39.791 ms
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=60.916 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=84.559 ms
^C
--- dev.sourcerebels.com ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 39.791/61.755/84.559/18.286 ms