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

Error al arrancar docker: Failed with result ‘protocol’

Ejecutamos systemctl status docker.service para mostrar la información del estado del servicio.

docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: failed (Result: protocol) since Fri 2017-12-29 10:05:40 CET; 2min 14s ago
     Docs: https://docs.docker.com
  Process: 14862 ExecStart=/usr/bin/dockerd -H fd:// (code=exited, status=1/FAILURE)
 Main PID: 14862 (code=exited, status=1/FAILURE)
      CPU: 43ms

dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Service hold-off time over, scheduling restart.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Stopped Docker Application Container Engine.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Start request repeated too quickly.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Failed to start Docker Application Container Engine.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.

A continuación Ejecutamos journalctl -xe para ver los logs del sistema.

dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Starting Docker Application Container Engine...
-- Subject: Unit docker.service has begun start-up
-- Defined-By: systemd
-- Support: http://www.ubuntu.com/support
-- 
-- Unit docker.service has begun starting up.
dic 29 10:05:40 Dell-XPS-8900 dockerd[14862]: ERROR: The 'disable-legacy-registry' configuration option has been removed. Interacting with legacy (v1) registries is no longer supported
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Main process exited, code=exited, status=1/FAILURE
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Failed to start Docker Application Container Engine.
-- Subject: Unit docker.service has failed
-- Defined-By: systemd
-- Support: http://www.ubuntu.com/support
-- 
-- Unit docker.service has failed.
-- 
-- The result is failed.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Unit entered failed state.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Failed with result 'protocol'.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: docker.service: Service hold-off time over, scheduling restart.
dic 29 10:05:40 Dell-XPS-8900 systemd[1]: Stopped Docker Application Container Engine.

Aquí se puede ver el motivo real del problema:

ERROR: The 'disable-legacy-registry' configuration option has been removed. Interacting with legacy (v1) registries is no longer supported

En el post Conectar a un Docker Registry inseguro en Ubuntu con Docker v17.06+ explicaba precisamente que una solución para acceder a un registry no securizado era modificar el fichero /etc/docker/daemon.json y añadir la línea:

"disable-legacy-registry": false,

Pues por lo visto esta opción de configuración ha sido deprecada en la versión actual de docker (17.12).

Se puede comprobar en el siguiente enlace.

Saludos

Conectar a un Docker Registry inseguro en Ubuntu con Docker v17.06+

Al actualizar los paquetes de mi instalación de Ubuntu, he visto que se ha actualizado docker.

A partir de ese momento no he podido hacer push/pull al registry que tenemos (está instalado de forma insegura en un servidor de la LAN).

En el access.log del registry veo las siguientes entradas de error 404:

84.88.79.211 - - [05/Jul/2017:07:55:17 +0000] "POST /v2/myimagename/blobs/uploads/ HTTP/1.1" 404 233 "-" "docker/17.06.0-ce go/go1.8.3 git-commit/02c1d87 kernel/4.4.0-83-generic os/linux arch/amd64 UpstreamClient(Docker-Client/17.06.0-ce \(linux\))"

Por defecto Docker, al hacer las peticiones contra el registry, trata de hacerlas contra los servicios “v2” y en caso de no encontrarlos intenta contra “v1”. En mi caso esto último no venía sucediendo.

En las release notes de la versión 16.03 veo lo siguiente:

Note: Docker 17.06 by default disables communication with legacy (v1) registries. If you require interaction with registries that have not yet migrated to the v2 protocol, set the --disable-legacy-registry=false daemon option. Interaction with v1 registries will be removed in Docker 17.12.

La solución pasa por actualizar el docker registry ya que en la versión 17.12 esta funcionalidad directamente no existirá. Mientras tanto, podemos añadir las siguientes líneas al fichero /etc/docker/daemon.json

{
  "disable-legacy-registry": false,
  "insecure-registries" : ["myregistryhost:port"]
}

Saludos