Faire dialoguer des objets avec MQTT

L’Internet des objets

Le sujet d’aujourd’hui est bien à la mode. Il y a beaucoup de hype autour de ce terme. Mais qu’est ce que c’est réellement ? Prenons la définition de whatis :

The Internet of Things (IoT) is a scenario in which objects, animals or people are provided with unique identifiers and the ability to transfer data over a network without requiring human-to-human or human-to-computer interaction. IoT has evolved from the convergence of wireless technologies, micro-electromechanical systems (MEMS) and the Internet.

D’après cette définition, l’IoT (Internet of things) est la possibilité d’avoir sa machine à laver branché au net. Idéal pour recevoir sur son smartphone un message pour indiquer que les caleçons sont propres. Cool.

En vrai, qui l’utilise ?

Bien entendu, il y a des utilisations plus sérieuse. La voiture qui contacte automatiquement les secours en cas d’accident. Le controle de la température et de l’humidité dans une serre. Le suivi des animaux dans une ferme. Etc.

Tous ces modules sont différents d’un point de vue du design, du nombre et du genre des capteurs, poids, taille, etc. Chaque module a ses propres besoins mais une contrainte revient assez souvent. Ils sont disséminé à travers le paysage et doivent fonctionner dans des conditions souvent difficiles. L’autonomie, la qualité de la réception, ou la quantité de données à transférer sont les principales difficultés quand on construit ce genre de module.

Pourquoi MQTT ?

Prenons le plus petit message envoyé par HTTP.

  • Header TCP; 20 bytes
  • Header IP: 20 bytes
  • Data: 1 byte

On se retrouve à envoyer au minimum 41 bytes pour un message. Prenons un module connecté par GSM avec une carte SIM limitant le traffic à 5Mo par mois (cas réél rencontré récemment), le nombre de message transféré sera très limité.

Prenons maintenant le même message envoyé par MQTT.

  • Message: 2 bytes

Desuite, il y a beaucoup moins de limitations. Prenons ensuite en compte la consomation avec le pourcentage de la batterie utilisée pour envoyer un message durant une expérience (source) ;

  • 0.01709% pour HTTP
  • 0.00010% pour MQTT

Là encore,  on voit bien l’intérêt de limiter le transfert de données.

 Comment ça marche ?

Le protocole en lui-même est assez simple. Connect, subscribe, publish. On publit des messages sur un topic dans un broker. D’autres personnes écoutent ce qui est publié et reçoivent le message.

Il y a plusieurs implémentations de MQTT, dans à peu près tous les langages (Ok, peut être pas en brainfuck). Je vais me baser sur PAHO pour Java.

	
<dependency>
  <groupId>org.eclipse.paho</groupId>
  <artifactId>mqtt-client</artifactId>
  <version>0.2.1</version>
</dependency>

Voilà l’implémentation la plus basique.

	private MqttClient mqttClient;
	
	public void connectToBroker(String clientId) throws Exception {
		final MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
		mqttClient = new MqttClient("tcp://iot.eclipse.org:1883", clientId);
		mqttClient.setCallback(this);
		mqttClient.connect(mqttConnectOptions);
	}
	
	public void subscribe(String topic) throws Exception {
		mqttClient.subscribe(topic, 0);
	}
	
	public void publish(String topic, String message) throws Exception {
		final MqttMessage mqttMessage = new MqttMessage(message.getBytes());
		MqttDeliveryToken mqttDeliveryToken = mqttClient.getTopic(topic).publish(mqttMessage);
		mqttDeliveryToken.waitForCompletion();
		Thread.sleep(100);
	}
	
	public void connectionLost(Throwable arg0) {
	}

	public void deliveryComplete(IMqttDeliveryToken arg0) {
	}

	public void messageArrived(String arg0, MqttMessage message) throws Exception {
		System.out.println(new String(message.getPayload()));
	}
	
	public static void main(String[] args) throws Exception {
		FullMqttPoc poc = new FullMqttPoc();
		poc.connectToBroker("publisher");
		poc.subscribe("topicPocMqtt");
		poc.publish("topicPocMqtt", "ceci est mon message");
	}

Tout d’abord, le broker. Pour cet exemple, j’ai utilisé le broker en libre accès fourni par la fondation Eclipse à l’adresse tcp://iot.eclipse.org:1883. Une large palette de broker est disponible pour de vrais projets comme Mosquitto ou HiveMQ. La connection se fait sans identifiants, mais une version sécurisé est disponible également. Pour notre exemple, seul un clientId est nécessaire, celui-ci devant être unique sur le broker.

La suite se fait par publish/subscribe. Dans notre exemple, un seul processus joue les deux rôles. La console affichera dont juste “ceci est mon message”. Un exemple plus complet est disponible ici avec deux processus distincts.

Design patterns: Le singleton

Le singleton est un design pattern (ou patron de conception si vous êtes franchouillard) qui permet de limiter l’instanciation d’une classe à un seul objet dans un système. C’est le design pattern le plus (mal) utilisé par les développeurs JAVA. Le principe de base du singleton est de bloquer l’accès au constructeur (en le mettant privé) et d’avoir une méthode qui retourne l’instance après l’avoir créée si elle n’existe pas déjà.

Le singleton représente des risques quand il est mal utilisé. En milieu multi-threadé, l’accès à l’instance peut causer des pertes de performance en cas de système synchronisé. Egalement, son utilisation à tort et à travers dans des classes XxxxxManager ou XxxxxUtils donne à certains des envies de mettre des baffes. La bonne pratique pour la création de ce genre de classe est toute simple: Ne le faites pas.

Lazy initialisation

Une méthode (trop) couramment utilisée pour créer le singleton. La création de l’instance est retardée jusqu’au moment où on en a rééllement besoin. Néanmoins, la méthode est synchronized. Il en résulte donc de piétres performances en environnement multithreadé. Si l’instance a besoin d’être accédée régulièrement, l’application pourra montrer d’importants ralentissements.

public class LazyInitialization {
	private static LazyInitialization instance = null;

	private LazyInitialization() {}

	public static synchronized LazyInitialization getInstance() {
		if (instance == null) {
			instance = new LazyInitialization();
		}
		return instance;
	}
}

Double checked locking

Une manière de limiter le problème du synchronized est de limiter la synchronisation au moment de la création. Néanmoins, celà ne fonctionne pas dans un environnement multithread. Il s’agit ici d’un bel exemple de “code juste” qui ignore ses fautes. Autrefois populaire, ce type de singleton est aujourd’hui considéré comme un antipattern.

public class DoubleCheckedLocking {
	private static volatile DoubleCheckedLocking instance = null;

	private DoubleCheckedLocking() { }

	public static DoubleCheckedLocking getInstance() {
		if (instance == null) {
			synchronized (DoubleCheckedLocking.class) {
				if (instance == null) {
					instance = new DoubleCheckedLocking();
				}
			}
		}
		return instance;
	}
}

Eager initialisation

Si le systéme doit utiliser une instance de manière très régulière, il est possible d’utiliser cette méthode. Il n’y a pas de synchronized, ce qui améliore les performances, et l’instance n’est créée que lorsque la classe est utilisée. L’instance est créée au chargement ce qui implique la présence en mémoire d’objet pas forcément utilisés et il n’y a pas de gestion des exceptions.

public class EagerInitialization {
	private static final EagerInitialization INSTANCE = new EagerInitialization();

	private EagerInitialization() { }

	public static EagerInitialization getInstance() {
		return INSTANCE;
	}
}

Static initialisation

Une variante est l’initialisation statique. La création dans un bloc statique permet la gestion des exceptions. Mais là encore, il y a en mémoire des objets qui ne sont pas encore utilisés.

public class StaticInitialization {
	private static final StaticInitialization instance;

	static {
		try {
			instance = new StaticInitialization();
		} catch (Exception e) {
			throw new RuntimeException("Error on singleton initialization", e);
		}
	}

	public static StaticInitialization getInstance() {
		return instance;
	}

	private StaticInitialization() {
	}
}

On demand holder

Cette version a été développée suivant les recherches de William Pugh de l’université du Maryland. Cette méthode fonctionne dans toutes les versions de Java et retarde au maximum l’initialisation et est totalement thread-safe sans nécessiter l’utilisation de volatile ou de synchronized. Il s’agit du pattern le plus populaire actuellement. La présence de la inner class permet de retarder la création de l’instance au moment de l’appel de la méthode getInstance().

public class OnDemandHolder {

	private OnDemandHolder() {
	}

	private static class SingletonHolder {
		private static final OnDemandHolder INSTANCE = new OnDemandHolder();
	}

	public static OnDemandHolder getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

Enum

L’auteur d’Effective JavaJoshua Bloch a presenté une solution basée sur enums. D’après lui, “a single-element enum type is the best way to implement a singleton”. Cette version ne fonctionne qu’à partir de Java 1.5. Le fait que ce soit un enum nous assure que la valeur est instantiée une seule fois dans le programme et est accessible globalement. Néanmoins, il n’y a pas de possibilité de lazy initialisation avec cette technique.

public enum Singleton {
    INSTANCE;
    public void execute (String arg) {
        // [...] 
    }
}

source: github

Introduction

Bonjours à tous et bienvenu sur mon blog dédié au code.

Je suis un développeur, passioné par mon métier, avec près de 10 ans d’expérience dans des domaines variés et sur toutes sortes de technologies. Surtout les pires.

Vous ne serez peut être pas d’accord avec moi. Parfois ou souvent. Ne vous génez pas pour le dire. Et le cas échéant, j’en tiendrai peut être compte. Ou pas.

Pour finir cette introduction, je voudrais vous citer mon professeur d’Ada95 et d’algorithmique, Daniel Feneuille:

Du code juste, c’est du code faux qui s’ignore