LiFi

De Ensiwiki
Aller à : navigation, rechercher


Project schedule.png
Titre du projet Communication avec de la lumière (LiFi)
Cadre Projets Réseaux Mobiles et Avancés

Équipe A​l​e​x​a​n​d​r​e P​o​d​l​e​w​s​k​iM​a​x​i​m​e T​u​r​l​u​r​e
Encadrants Franck Rousseau


Introduction

Le Li-Fi (pour Light Fidelity) est une technologie récente introduite en 2011 lors de la conférence TED. Elle se présente comme la remplaçante du Wi-Fi à moyen terme. L'utilisation du Li-Fi présente plusieurs avantages. Le premier est le débit. En théorie, un tel système est capable d'atteindre un débit de plusieurs centaines de gigabits par seconde. Mais ce débit est souvent moindre notamment, à cause de contraintes matérielles. Par exemple, en 2018, le meilleur produit grand public atteignait un débit d'environ 40Mbits/sec. Notre projet n'a pas pour vocation d'atteindre un tel débit (car nous n'avons pas les même moyens que cette entreprise), mais plutôt de faire découvrir les technologies mises en oeuvre pour reproduire un canal de transmission sans fil s'appuyant sur la lumière avec un matériel de base.


Matériel et logiciels

Matériel

  • 2 x Raspberry pi 3
  • 2 x Recepteur infrarouge arduino KY-022
  • 2 x LED infrarouge
  • 2 x Résistance 180Ω
  • 2 x Breadboard
  • Connectique diverse


Le récepteur infrarouge KY-022 réagit à un signal infrarouge ayant une fréquence de 38kHz. Il n'est pas nécessaire d'avoir un modèle de LED infrarouge particulier. Celle que nous utilisons ne tolère pas plus de 50mA d'intensité de courant c'est pourquoi nous ajoutons une résistance de 180Ω pour la protéger, en ayant une alimentation de 5V. Cette valeur est à adapter en fonction de la LED utilisée et/ou de la tension.


Logiciel

Pour utiliser les scripts fournis plus bas il est nécessaire d'avoir configuré les deux raspberry avec cette la distribution suivante : Linux Raspbian 4.14 disposant de la librairie pigpio pigpio installée.


Protocoles de transmission

On présente dans cette partie les protocoles que nous avons regardé pendant ce projet.

Le protocole IrDA

IrDA (Infrared Data Association) est un protocole utilisé pour la communication entre 2 appareils en utilisant un canal de transmission par lumière infrarouge. Le protocole IrDA est très simple puisqu'il consiste a envoyer un PULSE pour transmettre un '0' logique et rien pour transmettre un '1' logique.

Irda.png

Un message transmis avec ce protocole a une taille déterminée d'avance par les deux appareils communiquant. Typiquement un message contient 8 bits de charge utile encadrée par 1 bit (de valeur 0) au début et 1 bit (de valeur 1) à la fin de la transmission. Les deux appareils connaissent la durée d'un bit (appelé Bit Time) et effectue une mesure à chaque période. On peut réduire cette période pour augmenter le débit, mais cela nécessite du matériel performant.

L'avantage de cette modulation est l'efficacité de la transmission. Avec un tel protocole on peut transmettre des informations à un bon débit. En revanche, son utilisation nécessite d'avoir du matériel précis et la capacité de lire la valeur du capteur à des instants bien déterminés.

Notre récepteur effectuant un traitement sur le signal et ne nous permet pas de récupérer l'état de la photo-diode à un instant t.


Description du protocole NEC

Le protocole NEC est un protocole couramment utilisé dans la communication entre deux appareils en infrarouge. Typiquement on le retrouve dans la communication entre une télécommande et l'appareil qu'elle contrôle. La modulation utilisée est très simple mais permet d'être suffisamment fiable pour rarement perdre de l'information.

Basiquement la modulation utilisée comprend 2 états :

  • L'état PULSE : qui correspond à une suite de 0 et de 1 envoyé à la suite.
  • L'état GAP : qui correspond à une période ou aucun signal n'est envoyé.

Necmodulation.png

Pour former un '1' logique on envoie pendant 560μs un PULSE puis pendant 1,69ms un GAP. Pour former un '0' logique on envoie pendant 560μs un PULSE puis pendant 560μs un GAP.

Cette modulation permet de synchroniser simplement deux appareils ne possédant pas d'horloge commune. Le PULSE indique à l'appareil récepteur qu'une information va être envoyée à la fin de celui-ci et la durée entre 2 PULSE détermine le bit transmis (0 ou 1).

Cependant on comprend vite que ce genre de protocole ne permet pas la transmission d'une grande quantité de données. Il faut environ 1$ms$ pour transmettre 1 bit d'information. Ce qui nous permet tout au plus de transmettre à un débit de 1000bits/s. On est très loin des 40Mbits/s atteint par le meilleur dispositif Li-Fi disponible sur le marché. Le but de notre projet sera de trouver des optimisations pour améliorer le débit du protocole NEC existant.


Notre réalisation

Installation de raspbian sur raspberry

La solution la plus connue pour installer raspbian (le système d'exploitation créé pour raspberry pi) est d'utiliser un utilitaire appelé NOOB. L'inconvénient de cette solution est la nécessite de brancher le raspberry pi à un écran, et d'y connecter un clavier et une souris.


L'autre solution existante, et que nous avons utilisé, est de copier une image du système directement sur la carte micro SD. On trouve cette image sur la page https://raspbian-france.fr/telechargements/, et on utilise le logiciel Etcher pour mettre l'image sur la carte.

La dernière étape dans notre cas est d'activer le ssh sur le raspberry pi. Cela se fait en créant un fichier appelé "ssh" dans la partition boot, le fichier reste vide.

La carte SD peut donc être mise dans le raspberry pi. Une fois alimenté et connecté à un réseau on peux s'y connecter en ssh à l'adresse "raspberrypi.local" avec l'identifiant "pi" et le mot de passe "raspberry". On peut changer l'hostname avec la commande "sudo raspi-config".


Utilisation des ports GPIO

Pour la réalisation de ce projet, nous avons tout d'abord commencé par utiliser la librairie python RPi.GPIO, mais elle n'est pas adaptée pour l'envoi de signaux avec un timing précis.

Après quelques recherches, nous avons opté pour la librairie pigpio , qui permet à l'aide d'un service (pigpiod) d'avoir une gestion plus précise des ports GPIO.

Émission

La solution utilisée pour générer le signal voulu (suivant le protocole NEC) est le système de "Waves" dans pigpio. Ce système permet de définir un signal puis de demander au service fonctionnant au niveau du kernel de l'appliquer sur le pin GPIO voulu. Le principal problème de cette solution est la limite du nombre de transitions maximale dans la définition d'un signal. Cette limitation nous empêche de définir un signal envoyant plus de quelques dizaines d'octets.

Réception

La réception du signal doit également se faire de manière suffisamment précise pour correctement mesurer la durée d'un GAP. Plusieurs tentatives ont été réalisée, une première en python, regardant l'état du pin d'entrée jusqu'à un changement d'état, la mesure tu temps se fait lors de ce changement.

Une autre solution a été testée, elle consiste a laisser le service observer l'état d'un pin (1 fois par microseconde). Lors d'un changement une fonction callback est appelée avec la durée en microsecondes depuis le dernier changement. La fonction callback est également appelée régulièrement lorsqu'il n'y a pas de changement, nous permettant ainsi de détecter une fin de message après un temps défini.


N'ayant pas réussi a faire fonctionner la version du récepteur en C et ayant une version Python fonctionnelle, nous n'avons finalement pas utilisé la version en C. Cependant dans le cas où les limites rencontrés lors de ce projet seraient résolues, cette version serait bien plus performante.


Démonstration

Pour tester la transmission en utilisant la lumière infrarouge vous devez disposer du matériel décrit plus haut. Le système ce compose de deux acteurs (les 2 raspberry) qui vont communiquer via les LED et les récepteurs infrarouge. Pour chaque raspberry utilisez le schéma de branchement suivant :

Schema emitter-receptor bb.png Schema emitter-receptor schema.png


Lors du positionnement des 2 systèmes, il est important que chaque LED fasse face à au récepteur de l'autre système pour garantir une transmission sans perte. Le récepteur dispose d'un angle de détection d'environ 30 degrés. En théorie, notre système est capable de recevoir un signal même si les capteurs sont mal orientés grâce aux réflections sur les surfaces avoisinantes, mais pour une plus grande fiabilité nous recommandons de faire en sorte que les capteurs soient alignés.

Branchez chacun des raspberry en utilisant la connexion filaire est configurez votre interface réseau comme suit :

  • Mode : Manuel
  • Adresse : 169.254.0.2
  • Netmask : 255.255.0.0


Copiez les fichiers contenus dans le répertoire emitter\ et receiver\ sur les raspberry avec :

$ scp -r emitter pi@rpi1.local:~ # Pour le premier raspberry
$ scp -r emitter pi@rpi2.local:~ # Pour le second raspberry

$ scp -r receiver pi@rpi1.local:~
$ scp -r receiver pi@rpi2.local:~

# Mot de passe : raspberry

Compilez le programme permettant d'émettre avec :

$ cd ~/emitter
$ make

Lancez le démon pigpio :

$ sudo pigpiod -s1

Créez le fichier de votre choix dans répertoire emitter. Nous allons tenter de transmettre ce fichier à l'autre raspberry.

Pour cette démonstration, nous considérerons le premier raspberry comme un émetteur et le second comme récepteur. Sur le second raspberry lancez le script python receiver/receive2.py avec python3 receiver/receive2.py. Le récepteur est prêt à recevoir un fichier. Côté émetteur (sur le premier raspberry), créez un fichier et remplissez le avec le contenu de votre choix puis lancez l'emission du fichier avec ./fileIR <fichier>. Attendez la fin de l'émission (le programme d'émission termine). Une fois l'émission terminée, stoppez le récepteur avec Ctrl+C. Le résultat de la réception est écrit dans le fichier receiver/test.


Résultats

En testant notre système, nous parvenons à transmettre des fichiers. Nous observons quelques problèmes de réception dû à la fiabilité du canal utilisé lorsque l'on éloigne le récepteur et l'émetteur. Pour pallier à ces problèmes nous avons commencer à développer un code correcteur de Hamming(7,4) qui n'a malheureusement pas été incorporé au reste du projet.

D'après les tests effectué notre système est capable de transmettre de manière fiable à une distance d'environ 3m. En revanche le système peut être perturbé par des appareils externes tels que des télécommandes.

Nous avons tout de même été surpris pas la taille de la zone de détection du capteur. En effet, certains rayon infrarouges parviennent à rebondir sur les surfaces avoisinantes pour atteindre le récepteur. Nous pensons qu'avec une LED plus puissante nous aurions pu obtenir des meilleurs résultat (plus grande portée, meilleure fiabilité).


Limitations

Latence

Actuellement, le développement de ce moyen de communication nous a posé beaucoup de problèmes. Le principal problème de notre programme est le débit (moins de 1kbit/s). Cela est dû à l'utilisation du protocole NEC (et sa modulation). En effet, ce protocole est plus destiné à transmettre des commandes courtes entre deux appareils qu'à transmettre une grande quantité de données.

Fiabilité

Bien que la modulation du protocole NEC soit assez fiable, il arrive parfois que certaines informations soient perdues. La solution a cela serait d'implémenter un code correcteur d'erreur, typiquement Hamming(7,4) afin de détecter et de corriger un nombre raisonnable d'erreurs. Un début de script python utilisant Hamming(7,4) a été écrit dans le répertoire receiver/hamming.py. Nous avons souhaité l'incorporer dans notre projet mais comme nous n'avons pas un très bon débit, ajouter de la redondance n'aurait pas amélioré le débit.

Raspberry

Les raspberry on l'avantage d'être facile à prendre en main, en revanche ils ne sont pas adaptés pour la programmation bas niveau. En effet l'émission ou la réception de signaux infrarouges nécessite souvent d'exécuter des appels système et la documentation n'est pas toujours très claire à ce sujet. En général, le traitement des signaux s'effectue au niveau hardware.

Conclusion

Concevoir un système utilisant la lumière est une tâche compliquée. Utiliser la lumière comme canal de transmission nécessite du matériel spécialisé que nous n'avons pas pu trouver/nous procurer (par exemple une photo-diode). Un circuit spécialisé qui permettrait d'établir un canal fiable entre deux appareils nous aurait permis plus simplement d'établir un protocole de communication entre les deux appareils et donc nous concentrer sur l'aspect routage de notre système (abstraire l'utilisation de l'infrarouge).


Bibliograpie


Code source utilisé

receiver.py

 1 mport RPi.GPIO as GPIO
 2 import math
 3 import os
 4 from datetime import datetime
 5 from time import sleep
 6 
 7 from struct import pack
 8 
 9 # This is for revision 1 of the Raspberry Pi, Model B
10 # This pin is also referred to as GPIO24
11 INPUT_WIRE = 18
12 
13 
14 # 10000 is arbitrary, adjust as necessary
15 TIMEOUT_AFTER = 10000
16 
17 ZERO_LENGTH = 400
18 ONE_LENGTH = 800
19 
20 MOY_LENGTH = (ZERO_LENGTH + ONE_LENGTH) // 2
21 
22 
23 GPIO.setmode(GPIO.BOARD)
24 GPIO.setup(INPUT_WIRE, GPIO.IN)
25 
26 PULSE = 0
27 GAP = 1
28 
29 
30 def bitstring_to_bytes(array):
31 	v = int("".join(array), 2)
32 	b = bytearray()
33 	while v:
34 		b.append(v & 0xff)
35 		v >>= 8
36 	return bytes(b[::-1])
37 
38 
39 def main():
40 	print("waiting for data to write on test")
41 	with open("test", "wb") as f:
42 		for data in get_data():
43 			if len(data) == 0:
44 				continue
45 			f.write(bitstring_to_bytes(data))
46 			print("data written ({} bits)".format(len(data)))
47 
48 
49 def get_data():
50 	while True:
51 		value = 1
52 		# Loop until we read a 0
53 		while value:
54 			value = GPIO.input(INPUT_WIRE)
55 
56 		# Grab the start time of the command
57 		startTime = datetime.now()
58 
59 		# received data
60 		data = []
61 
62 		# The end of the "command" happens when we read more than
63 		# a certain number of 1s (1 is off for my IR receiver)
64 		numOnes = 0
65 
66 		# Used to keep track of transitions from 1 to 0
67 		previousVal = 0
68 		state = PULSE
69 
70 		while True:
71 			if state == PULSE:
72 				if value == GAP:
73 					state = GAP
74 					startTime = datetime.now()
75 				numOnes = 0
76 			else:
77 				if value == PULSE:
78 					state = PULSE
79 					time_interval = (datetime.now() - startTime).microseconds
80 					data.append(["0", "1"][time_interval > MOY_LENGTH])
81 
82 				numOnes = numOnes + 1
83 				if numOnes > TIMEOUT_AFTER:
84 					break
85 
86 			value = GPIO.input(INPUT_WIRE)
87 
88 		yield data
89 
90 if __name__ == "__main__":
91 	main()

main.c (émetteur)

 1 #include <stdlib.h>
 2 #include <stdint.h>
 3 #include <stdio.h>
 4 #include "irslinger.h"
 5 
 6 // Size of block transmited in bits
 7 #define BLOCK_SIZE 2048
 8 
 9 int main(int argc, char *argv[])
10 {
11     // The Broadcom pin number the signal will be sent on
12 	uint32_t outPin = 23;
13 	// The frequency of the IR signal in Hz
14 	int frequency = 38000;           
15 	// The duty cycle of the IR signal. 0.5 means for every cycle,
16 	// the LED will turn on for half the cycle time, and off the other half
17 	double dutyCycle = 0.5;         
18 	                      
19 	// The duration of the beginning pulse in microseconds                                 
20 	int leadingPulseDuration = 0; 
21 	// The duration of the gap in microseconds after the leading pulse
22 	int leadingGapDuration = 0;
23 	// The duration of a pulse in microseconds when sending a logical 1
24 	int onePulse = 500;  
25 	// The duration of a pulse in microseconds when sending a logical 0
26 	int zeroPulse = onePulse;
27 	// The duration of the gap in microseconds when sending a logical 0
28 	int zeroGap = 400;      
29 	// The duration of the gap in microseconds when sending a logical 1
30 	int oneGap = 2 * zeroGap;  
31 	// 1 = Send a trailing pulse with duration equal to "onePulse"
32 	// 0 = Don't send a trailing pulse
33 	int sendTrailingPulse = 1;       
34 	                                 
35 
36 	if (argc != 2) {
37 		printf("Correct usage:\n%s <filename>\n", argv[0]);
38 		exit(EXIT_FAILURE);
39 	}
40 
41 	FILE *file = fopen(argv[1], "rb");
42 	if (file == NULL) {
43 		perror("Unable to open file");
44 		exit(EXIT_FAILURE);
45 	}
46 
47 	uint8_t raw_data[256] = {0};
48 	char code[2049] = {0};
49 
50 	int result_read;
51 
52 	int result = 0;
53 
54 	while (!result && (result_read = fread(
55 			&raw_data,
56 			sizeof(uint8_t),
57 			15 /* experimental value, depends of pulse length */,
58 			file)) != 0) {
59 		for (int i = 0; i < result_read; i++) {
60 			for (int j = 0; j < 8; j++) {
61 				uint8_t bit = (raw_data[i] >> (7 - j)) & 0x1;
62 				code[i*8 + j] = '0' + bit;
63 			}
64 		}
65 
66 		code[result_read * 8 + 1] = 0;
67 
68 		printf("READ %d bytes from file\n", result_read);
69 
70 		result = irSling(
71 			outPin,
72 			frequency,
73 			dutyCycle,
74 			leadingPulseDuration,
75 			leadingGapDuration,
76 			onePulse,
77 			zeroPulse,
78 			oneGap,
79 			zeroGap,
80 			sendTrailingPulse,
81 			code);
82 	}
83 
84 	return result;
85 }

irslinger.h

Code adapté de https://github.com/bschwind/ir-slinger pour utiliser le service pigpiod

  1 #ifndef IRSLINGER_H
  2 #define IRSLINGER_H
  3 
  4 #include <string.h>
  5 #include <math.h>
  6 #include <pigpiod_if2.h>
  7 
  8 #define MAX_COMMAND_SIZE 2900
  9 #define MAX_PULSES 12000
 10 
 11 static int pi;
 12 
 13 static inline void addPulse(uint32_t onPins, uint32_t offPins, uint32_t duration, 
 14 gpioPulse_t *irSignal, int *pulseCount)
 15 {
 16     int index = *pulseCount;
 17 
 18 	irSignal[index].gpioOn = onPins;
 19 	irSignal[index].gpioOff = offPins;
 20 	irSignal[index].usDelay = duration;
 21 
 22 	(*pulseCount)++;
 23 }
 24 
 25 // Generates a square wave for duration (microseconds) at frequency (Hz)
 26 // on GPIO pin outPin. dutyCycle is a floating value between 0 and 1.
 27 static inline void carrierFrequency(uint32_t outPin, double frequency, 
 28 double dutyCycle, double duration, gpioPulse_t *irSignal, int *pulseCount)
 29 {
 30 	double oneCycleTime = 1000000.0 / frequency; // 1000000 microseconds in a second
 31 	int onDuration = (int)round(oneCycleTime * dutyCycle);
 32 	int offDuration = (int)round(oneCycleTime * (1.0 - dutyCycle));
 33 
 34 	int totalCycles = (int)round(duration / oneCycleTime);
 35 	int totalPulses = totalCycles * 2;
 36 
 37 	int i;
 38 	for (i = 0; i < totalPulses; i++)
 39 	{
 40 		if (i % 2 == 0)
 41 		{
 42 			// High pulse
 43 			addPulse(1 << outPin, 0, onDuration, irSignal, pulseCount);
 44 	    }
 45 		else
 46 		{
 47 			// Low pulse
 48 			addPulse(0, 1 << outPin, offDuration, irSignal, pulseCount);
 49 		}
 50 	}
 51 }
 52 
 53 // Generates a low signal gap for duration, in microseconds, on GPIO pin outPin
 54 static inline void gap(uint32_t outPin, double duration, 
 55 gpioPulse_t *irSignal, int *pulseCount)
 56 {
 57 	addPulse(0, 0, duration, irSignal, pulseCount);
 58 }
 59 
 60 static inline int irSling(uint32_t outPin,
 61 	int frequency,
 62 	double dutyCycle,
 63 	int leadingPulseDuration,
 64 	int leadingGapDuration,
 65 	int onePulse,
 66 	int zeroPulse,
 67 	int oneGap,
 68 	int zeroGap,
 69 	int sendTrailingPulse,
 70 	const char *code)
 71 {
 72 	if (outPin > 31)
 73 	{
 74 		// Invalid pin number
 75 		return 1;
 76 	}
 77 
 78 	size_t codeLen = strlen(code);
 79 
 80 	if (codeLen > MAX_COMMAND_SIZE)
 81 	{
 82 		// Command is too big
 83 		return 1;
 84 	}
 85 
 86 	gpioPulse_t irSignal[MAX_PULSES];
 87 	int pulseCount = 0;
 88 
 89 	// Generate Code
 90 	carrierFrequency(outPin, frequency, dutyCycle, leadingPulseDuration, 
 91 	irSignal, &pulseCount);
 92 	gap(outPin, leadingGapDuration, irSignal, &pulseCount);
 93 
 94 	int i;
 95 	for (i = 0; i < codeLen; i++)
 96 	{
 97 		if (code[i] == '0')
 98 		{
 99 			carrierFrequency(outPin, frequency, dutyCycle, zeroPulse, 
100 			irSignal, &pulseCount);
101 			gap(outPin, zeroGap, irSignal, &pulseCount);
102 		}
103 		else if (code[i] == '1')
104 		{
105 			carrierFrequency(outPin, frequency, dutyCycle, onePulse, 
106 			irSignal, &pulseCount);
107 			gap(outPin, oneGap, irSignal, &pulseCount);
108 		}
109 		else
110 		{
111 			printf("Warning: Non-binary digit in command\n");
112 		}
113 	}
114 
115 	if (sendTrailingPulse)
116 	{
117 		carrierFrequency(outPin, frequency, dutyCycle, onePulse, 
118 		irSignal, &pulseCount);
119 	}
120 
121 	printf("pulse count is %i\n", pulseCount);
122 	// End Generate Code
123 
124 	// Init pigpio
125 	if (pi = pigpio_start(NULL, NULL) < 0)
126 	{
127 		// Initialization failed
128 		printf("GPIO Initialization failed\n");
129 		return 1;
130 	}
131 
132 	// Setup the GPIO pin as an output pin
133 	set_mode(pi, outPin, PI_OUTPUT);
134 
135 	// Start a new wave
136 	wave_clear(pi);
137 
138 	wave_add_generic(pi, pulseCount, irSignal);
139 	int waveID = wave_create(pi);
140 
141 	if (waveID >= 0)
142 	{
143 		int result = wave_send_using_mode(pi, waveID, PI_WAVE_MODE_ONE_SHOT);
144 
145 		printf("Result: %i\n", result);
146 	}
147 	else
148 	{
149 		printf("Wave creation failure!\n %i", waveID);
150 	}
151 
152 	// Wait for the wave to finish transmitting
153 	while (wave_tx_busy(pi))
154 	{
155 		time_sleep(0.1);
156 	}
157 
158 	// Delete the wave if it exists
159 	if (waveID >= 0)
160 	{
161 		wave_delete(pi, waveID);
162 	}
163 
164 	// Cleanup
165 	pigpio_stop(pi);
166 	pi = -1;
167 	return 0;
168 }
169 
170 #endif