fbpixel
Utilisation des WebSockets avec React Native

Utilisation des WebSockets avec React Native

Nous allons créer une application React Native qui servira de client Websockets et pourra communiquer avec un serveur distant. WebSockets est un protocole populaire de communication web simple et robuste permettant la communication en temps réel entre client et serveur.

Matériel

  • Appareil Android
  • Ordinateur pour la programmation
  • Un câble USB pour connecter l’appareil Android au PC

Configuration du projet React Native

Pour venir lire et écrire dans un fichier nous allons utiliser la librairie File System, react-native-fs

npm install --save react-use-websocket

Utilisation de la librairie

A partir de la librairie, nous importons les objets et fonctions qui nous intéresse

import useWebSocket, { ReadyState } from 'react-use-websocket';
  • ReadyState état de la connexion avec le serveur
  • useWebScoket permet d’initialiser une connexion websockets

Nous créons ensuite un composant fonctionnel avec les états désirés

  const [ipAddress, setIpAddress] = useState('');
  const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765');
  const [messageText, setMessageText] = useState("");
  const [messageHistory, setMessageHistory] = useState([]);
  const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer);

  const connectionStatus = {
    [ReadyState.CONNECTING]: 'Connecting',
    [ReadyState.OPEN]: 'Open',
    [ReadyState.CLOSING]: 'Closing',
    [ReadyState.CLOSED]: 'Closed',
    [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
  }[readyState];

  const paramCmd = {
    type: 'setParam',
    param1: 30,
    param2: 2.3,
  }

Dans les états nous récupérons les fonctions et états de useWebSokcet

  • sendMessage la fonction pour envoyer des messages au format String
  • sendJsonMessage pour pour envoyer des messages au format JSON
  • lastMessage contient la réponse du serveur au format String
  • lastJsonMessage contient le dernier message du serveur au format JSON
  • readyState contient l’état de la connexion

Nous définissons aussi une constante au format Json paramCmd.

Nous utilisons un hook useEffect dans lequel nous récupérons l’adresse IP de l’appareil et nous gérons les messages reçus du serveur.

  useEffect(() => {
    const fetchIpAddress = async () => {
      const ip = await NetworkInfo.getIPV4Address();
      setIpAddress(ip);
      console.log("ip adresses ; ", ip)
    };

    fetchIpAddress();
	
    if (lastMessage !== null) {
      setMessageHistory((prev) => prev.concat(lastMessage));
    }
    if (lastJsonMessage !== null) {
      console.log(JSON.stringify(lastJsonMessage));
    }
    return () => {
    };
  }, [lastMessage, setMessageHistory,lastJsonMessage]);

Enfin, nous créons le rendu de l’application avec les éléments suivant

  • un texte pour afficher l’adresse IP de l’appareil
  • un bouton pour envoyer un message JSON
  • zone de texte pour le texte à écrire pour écrire le message au format texte
  • bouton Send pour envoyer le message
  • zone de texte pour entrer l’adresse du serveur
  • zone de texte pour afficher les réponses du serveur
  return (
    <View style={styles.mainBody}>
      <Text
        style={styles.mainTitle}>
        AC Websocket Terminal
      </Text>
      <ScrollView>

      <View style={styles.deviceItem}>
                      <View style={{flex:2}}>
                        <Text style={styles.deviceName}>{ipAddress}</Text>
                        <Text style={styles.deviceInfo}>{connectionStatus}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() => sendJSON()}
                        disabled={readyState !== ReadyState.OPEN}
                        style={styles.deviceButton}>
                        <Text
                          style={styles.buttonText}>
                          Send JSON
                        </Text>
                      </TouchableOpacity>
                    </View>


      <View
              style={styles.inputBar}>        
              <TextInput
                style={styles.textInput}
                placeholder="Enter a message"
                value={messageText}
                onChangeText={(text) =>    setMessageText(text)
                }
              />
              <TouchableOpacity
                        onPress={() => sendMessage(messageText)}
                        disabled={readyState !== ReadyState.OPEN}
                        style={[styles.sendButton]}>
                        <Text
                          style={styles.buttonText}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>

      <TextInput
        placeholder="Server IP"
        onChangeText={setIpServer}
        value={ipServer}
      />
      <View style={{flex:1,minHeight:200}}>
      <Text>Received Message:</Text>
              <ScrollView style={styles.textOutput}>
                {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null}
                
                {messageHistory.map((message, idx) => (
                  <Text key={idx}>{message ? message.data : null}</Text>
                ))}
              </ScrollView>
              </View>
      </ScrollView>
    </View>
  );

Création d’un serveur WebSockets avec Python

Pour tester notre application, nous créons un serveur websockets sur le PC

#!/usr/bin/env python
# python3 -m pip install websockets

import json
import asyncio
from websockets.server import serve

def getIpAddress():
    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("8.8.8.8", 80))
    ipaddress = s.getsockname()[0]
    s.close()
    return ipaddress

ipaddress = getIpAddress()
port = 8765

async def echo(websocket):
    async for message in websocket:
        print("received from {}:{} : ".format(websocket.remote_address[0],websocket.remote_address[1]) + message)
        
        if('{' not in message):
            await websocket.send(message)
        else:
            request = json.loads(message)
            answer = {}
            if(request['type'] == 'setParam'):
                answer['type'] = request['type']
                if(request['param1']<100 and request['param2']>1.2):
                    answer['valid'] = True
                    for key, val in request.items():
                        print("\t"+key+": ", val)
                else:
                    answer['valid'] = False
            else:
                answer['type'] = 'unknown'
                answer['valid'] = False
                
            await websocket.send(json.dumps(answer))

async def main():
    print("Server is activated on ws://{}:{}".format(ipaddress,port))
    #async with serve(echo, "localhost", 8765):
    async with serve(echo, "0.0.0.0", port):
        await asyncio.Future()  # run forever

asyncio.run(main())

Résultat

Grâce à cette application, nous pouvons envoyer des String et JSON au serveur et afficher ses réponses

Code complet de communication WebSockets avec React Native

/**
 * https://reactnative.dev/docs/network
 * https://www.npmjs.com/package/react-use-websocket
 * https://github.com/robtaussig/react-use-websocket
 * test on python
 * https://websockets.readthedocs.io/en/stable/
 */

import React, {useState, useEffect, useCallback} from 'react';
import {   
  View, 
  ScrollView, 
  Text,
  TextInput,
  TouchableOpacity, 
  StyleSheet} from 'react-native';
import { NetworkInfo } from 'react-native-network-info'
import useWebSocket, { ReadyState } from 'react-use-websocket';



const WebsocketTerminal = () =>  {

  const [ipAddress, setIpAddress] = useState('');
  const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765');
  const [messageText, setMessageText] = useState("");
  const [messageHistory, setMessageHistory] = useState([]);
  const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer);


  useEffect(() => {
    const fetchIpAddress = async () => {
      const ip = await NetworkInfo.getIPV4Address();
      setIpAddress(ip);
      console.log("ip adresses ; ", ip)
    };

    fetchIpAddress();
	
    if (lastMessage !== null) {
      setMessageHistory((prev) => prev.concat(lastMessage));
    }
    if (lastJsonMessage !== null) {
      console.log(JSON.stringify(lastJsonMessage));
    }
    return () => {
    };
  }, [lastMessage, setMessageHistory,lastJsonMessage]);

  const connectionStatus = {
    [ReadyState.CONNECTING]: 'Connecting',
    [ReadyState.OPEN]: 'Open',
    [ReadyState.CLOSING]: 'Closing',
    [ReadyState.CLOSED]: 'Closed',
    [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
  }[readyState];

  const sendJSON = () => {
    const paramCmd = {
      type: 'setParam',
      param1: 30,
      param2: 2.3,
    }

    sendJsonMessage(paramCmd);

  }
  
  return (
    <View style={styles.mainBody}>
      <Text
        style={styles.mainTitle}>
        AC Websocket Terminal
      </Text>
      <ScrollView>

      <View style={styles.deviceItem}>
                      <View style={{flex:2}}>
                        <Text style={styles.deviceName}>{ipAddress}</Text>
                        <Text style={styles.deviceInfo}>{connectionStatus}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() => sendJSON()}
                        disabled={readyState !== ReadyState.OPEN}
                        style={styles.deviceButton}>
                        <Text
                          style={styles.buttonText}>
                          Send JSON
                        </Text>
                      </TouchableOpacity>
                    </View>


      <View
              style={styles.inputBar}>        
              <TextInput
                style={styles.textInput}
                placeholder="Enter a message"
                value={messageText}
                onChangeText={(text) =>    setMessageText(text)
                }
              />
              <TouchableOpacity
                        onPress={() => sendMessage(messageText)}
                        disabled={readyState !== ReadyState.OPEN}
                        style={[styles.sendButton]}>
                        <Text
                          style={styles.buttonText}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>

      <TextInput
        placeholder="Server IP"
        onChangeText={setIpServer}
        value={ipServer}
      />
      <View style={{flex:1,minHeight:200}}>
      <Text>Received Message:</Text>
              <ScrollView style={styles.textOutput}>
                {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null}
                
                {messageHistory.map((message, idx) => (
                  <Text key={idx}>{message ? message.data : null}</Text>
                ))}
              </ScrollView>
              </View>
      </ScrollView>
    </View>
  );
}

export default WebsocketTerminal;


let BACKGROUND_COLOR = "#161616"; //191A19
let BUTTON_COLOR = "#346751"; //1E5128
let ERROR_COLOR = "#C84B31"; //4E9F3D
let TEXT_COLOR = "#ECDBBA"; //D8E9A8
var styles = StyleSheet.create({

  mainBody: { flex: 1, justifyContent: 'center',  backgroundColor: BACKGROUND_COLOR},

  mainTitle:{
    color: TEXT_COLOR,
    fontSize: 30,
    textAlign: 'center',
    borderBottomWidth: 2,
    borderBottomColor: ERROR_COLOR,
  },

  backgroundVideo: {
    borderWidth: 2,
    borderColor: 'red',
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },

  webVideo: {
    borderWidth: 2,
    borderColor: 'green',
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  },

  buttonText: {
    color: TEXT_COLOR,
    fontWeight: 'bold',
    fontSize: 12,
	  textAlign: 'center',
    textAlignVertical: 'center',
  },

  sendButton: {
    backgroundColor: BUTTON_COLOR,
    padding: 15,
    borderRadius: 15,
    margin: 2,
    paddingHorizontal: 20,
    },

    deviceItem: {
      flexDirection: 'row',
      flex: 3,
      marginBottom: 2,
    },
    deviceName: {
      fontSize: 14,
      fontWeight: 'bold',
    },
    deviceInfo: {
      fontSize: 8,
    },
    deviceButton: {
      backgroundColor: '#2196F3',
      padding: 10,
      borderRadius: 10,
      margin: 2,
      paddingHorizontal: 20,
    },

  inputBar:{
    flexDirection: 'row',
    justifyContent: 'space-between',
    margin: 5,
  },  

  textInput:{
    backgroundColor: '#888888',
    margin: 2,
    borderRadius: 15,
    flex:3,
  },

  textOutput:{
    backgroundColor: '#333333',
    margin: 10,
    borderRadius: 2,
    borderWidth: 1,
    borderColor: '#EEEEEE',
    textAlignVertical: 'top',
  }

});

Sources

Communication entre serveur et client WebSockets avec Python

Communication entre serveur et client WebSockets avec Python

Nous allons voir comment mettre en place une communication entre un serveur et un client en utilisant le protocole Websockets sous Python. WebSockets est un protocole de communication web simple et robuste permettant la communication en temps réel.

Installation de la librairie Websockets

Pour utiliser les WebSockets avec Python, nous installons le paquet nécessaire..

python3 -m pip install websockets

Nous utilisons aussi la librairie asyncio qui permet de faire de la programmation asynchrone pour le développement de serveur performant

Récupérer l’adresse IP du serveur

Comme dans toute communication internet, pour établir une connexion entre le client et le serveur, il faut connaître l’addresse IP du serveur.

Pour récupérer l’adresse IP de la machine serveur vous pouvez utiliser les commandes ipconfig (Windows) ou ifconfig/ ip addr (Linux) (ici: 192.168.1.59)

Il est aussi possible d’utiliser le paquet socket dans le script Python

def getIpAddress():
	import socket
	s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	s.connect(("8.8.8.8", 80))
	ipaddress = s.getsockname()[0]
	s.close()
	return ipaddress

ipaddress = getIpAddress()

Notez l’adresse, vous l’utiliserez dans le code client.

Code Python pour le serveur WebSocket

Pour lancer le serveur, nous définissons une fonction main qui va ouvrir le serveur sur le port 8765 et le faire tourner en boucle. Nous appelons aussi la fonction callback echo() qui va gérer la réception de message. Dans cet exemple, nous renvoyons le message tel quel au client.

#!/usr/bin/env python
# python3 -m pip install websockets

import asyncio
from websockets.server import serve

def getIpAddress():
	import socket
	s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	s.connect(("8.8.8.8", 80))
	ipaddress = s.getsockname()[0]
	s.close()
	return ipaddress

ipaddress = getIpAddress()
port = 8765

async def echo(websocket):
	async for message in websocket:
		print("received from {}:{} : ".format(websocket.remote_address[0],websocket.remote_address[1]) + message)
		await websocket.send(message)

async def main():
	print("Server is activated on ws://{}:{}".format(ipaddress,port))
	#async with serve(echo, "localhost", 8765):
	async with serve(echo, "0.0.0.0", port):
		await asyncio.Future()  # run forever

asyncio.run(main())

N.B.: pour que le serveur soit visible sur le réseau extérieur à l’ordinateur vous devez spécifier l’adresse “0.0.0.0” à la place de “localhost”

Code Python pour le client WebSocket

Dans le code client, nous nous connectons au serveur en spécifiant l’adresse IP et le port. Puis, nous envoyons un message. Enfin on attend le message de réponse pour l’afficher.

#!/usr/bin/env python
# python3 -m pip install websockets

import asyncio
from websockets.sync.client import connect

def hello():
    #with connect("ws://localhost:8765") as websocket:
    with connect("ws://192.168.1.52:8765") as websocket:
    
        websocket.send("Hello world!")
        message = websocket.recv()
        print(f"Received from server : {message}")

hello()

Résultat

Dans un terminal, lancez d’abord le script serveur: python websocket_server.py

Dans un second terminal, lancez ensuite le script client ; python websocket_client.py

Échange de message JSON via WebSocket

Le format JSON(JavaScript Object Notation) est un format d’échange de donnée très populaire dans la communication web. NousPour échanger des données JSON entre serveur et client nous utilisons la libraire pré-installée json

Dans le script client, nous allons envoyer une requête au format JSON. Le serveur va recevoir cette requête vérifier les valeurs et envoyer une réponse avec le résultat de la validation.

Les fonctions à connaitre pour gérer le format JSON sont:

  • json.loads() pour passer d’un String à un dict
  • json.dumps() pour passer d’un dict à un String

D’autre format Python peuvent être converti en leur équivalent JSON

PythonJSON
dictobject
list, tuplearray
strstring
int, float, intnumber
Truetrue
Falsefalse
Nonenull

script client

#!/usr/bin/env python
# python3 -m pip install websockets
import json
import asyncio
from websockets.sync.client import connect

jsondata = {"type": "setParam", "param1": 30, "param2": 2.3}
jsonwrong = {"type": "setParam", "param1": 30, "param2": -2.3}
jsonunkn = {"type": "setData", "param1": 30, "param2": 2.3}

def hello():
	with connect("ws://192.168.1.52:8765") as websocket:
		websocket.send("Hello world!")
		message = websocket.recv()
		print(f"Received from server : {message}")

		websocket.send(json.dumps(jsondata))
		message = websocket.recv()
		print(f"Received from server : {message}")

		websocket.send(json.dumps(jsonwrong))
		message = websocket.recv()
		print(f"Received from server : {message}")
				
		websocket.send(json.dumps(jsonunkn))
		message = websocket.recv()
		print(f"Received from server : {message}")

hello()

script serveur

#!/usr/bin/env python
# python3 -m pip install websockets

import json
import asyncio
from websockets.server import serve

def getIpAddress():
	import socket
	s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	s.connect(("8.8.8.8", 80))
	ipaddress = s.getsockname()[0]
	s.close()
	return ipaddress

ipaddress = getIpAddress()
port = 8765

async def echo(websocket):
	async for message in websocket:
		print("received from {}:{} : ".format(websocket.remote_address[0],websocket.remote_address[1]) + message)
		
		if('{' not in message):
			await websocket.send(message)
		else:
			request = json.loads(message)
			answer = {}
			if(request['type'] == 'setParam'):
				answer['type'] = request['type']
				if(request['param1']<100 and request['param2']>1.2):
					answer['valid'] = True
					for key, val in request.items():
						print("\t"+key+": ", val)
				else:
					answer['valid'] = False
			else:
				answer['type'] = 'unknown'
				answer['valid'] = False
				
			await websocket.send(json.dumps(answer))

async def main():
	print("Server is activated on ws://{}:{}".format(ipaddress,port))
	#async with serve(echo, "localhost", 8765):
	async with serve(echo, "0.0.0.0", port):
		await asyncio.Future()  # run forever

asyncio.run(main())

Résultat

Sources

Envoyer de longues chaînes de caractères via BLE

Envoyer de longues chaînes de caractères via BLE

Le Bluetooth Low Energy (BLE) a une limitation connue d’une 20Bytes pour la longueur des chaînes envoyées. Ils existent des méthodes pour outrepasser cette limite.

Matériel

Dans ce tutoriel, nous envoyons des données à partir d’une application Android, développée sous React Native, vers une ESP32. Les méthodes décrites peuvent être valables avec d’autres appareils.

  • Appareil Android
  • ESP32 BLE

Limitation de 20 bytes du BLE

L’application développée sous React Native est tout à fait capable d’envoyer de longues chaînes de caractères sous forme de paquet de 20bytes par défaut. Sans modification du tutoriel précédent voici la valeur reçue côté ESP32. Seul le dernier paquet est gardé en mémoire.

01:48:39.734 -> ESP32BLE server ready
01:48:40.493 -> Server address:3c:61:05:31:5f:12
01:48:41.672 -> Client connected
01:48:48.169 -> Charac value: Helloworldthisisalon
01:48:48.253 -> Charac value: gstringletseewhatcom
01:48:48.374 -> Charac value: esout;
01:48:48.374 -> this is the last packet

Résumé des méthodes pour envoyer de longues chaînes via BLE

  • Récupérer plusieurs paquets (côté récepteur)
  • Envoyer la longue chaîne sous forme de petits paquets (côté émetteur)
  • Augmenter la limite MTU de 23 bytes à 512 bytes

Récupérer plusieurs paquets

Si l’appareil qui envoie les messages est capable de gérer l’envoie de chaine de caractères qui dépassent la limite MTU, comme c’est le cas dans notre exemple, il ne reste plus qu’à configurer le module récepteur pour concaténer les différents paquets reçus. Pour cela, nous utilisons un caractères de fin de chaîne, par exemple “;”.

Dans la fonction onWrite de la caractéristique du module ESP32BLE, nous ajoutons un test de fin de chaîne et une variable longMsg qui nous servira de tampon.

std::string longMsg;
class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string value = pCharacteristic->getValue();

      if (value.length() > 0) {
        Serial.print("Charac value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);

        Serial.println();
        if(value.find(";") != -1){
          //Serial.println("this is the last packet");
          longMsg = longMsg + value;
          Serial.print("longMsg: ");
          for (int i = 0; i < longMsg.length(); i++)
            Serial.print(longMsg[i]);
          Serial.println();
          //do whatever you want with longMsg then erase
          longMsg = "";
        }else{
          longMsg = longMsg + value;
        }
      }
    }
};

Envoyer des paquets de taille prédéfinie

Si l’appareil qui envoie les données ne gère pas les messages de taille supérieure au MTU, alors il est possible de séparer la chaîne de caractères en paquets de taille prédéfinie (chunksize). Cette méthode permet de maitriser la taille des paquets envoyés.

Dans le projet React Native, nous créons une fonction qui va transformer la longue chaîne de caractère en Array de bytes que nous allons envoyez par paquets.

    const sendLongMessage = async (data: string, chunksize: number) => {
      console.log("data : ",data)
      var bytes = convertStringToByteArray(data)
      var times = Math.ceil(bytes.length/chunksize);
        var packets = [];
  
        console.log("split data ",times," times");
        //console.log("bytes: ",bytes)
        var i=0
        for(var time=0;time<times;time++){
          var packet = bytes.slice(time*chunksize, (time+1)*chunksize)
          packets.push(packet)
          console.log(time," : ",byteArrayToString(packet))
          sendMessage(byteArrayToString(packet));
          await delay(100);
        }
    }

N.B.: une fonction de retard peut être introduite pour laisser le temps au message d’être envoyé. delay(100)

const delay = (ms: number) => new Promise(
  resolve => setTimeout(resolve, ms)
);

Augmenter la limite MTU de l’application

Par défaut, la librairie react-native-ble-manager envoie des paquets de 20bytes, Il existe une méthode permettant de spécifier au module BLE de modifier la taille du Maximum Transmission Unit (MTU)

Dans le code de l’application, pendant la connexion, avant de récupérer les services, ajouter une requête MTU

BleManager.requestMTU(device.id, 512).then(
        (mtu) => {
          setMTU(mtu);
        }
      ) //request the MTU size in bytes

Lors de l’envoie du message, vous pouvez spécifier la taille maximum du message en bytes, “mtu”, dans la fonction BLEManager.write()

     BleManager.write(
       selectedDevice.id,
       serviceid,
       caracid,
       buffer.toJSON().data,
       mtu // or 512
     ).then(() => {
       // Success code
       console.log("Write: " + buffer.toJSON().data);
     })

Côté ESP32, il existe une méthode de la librairie BLEDevice permettant de définir la taille du MTU.

BLEDevice::setMTU(512); //between 23 and 517 (bluetooth 4.2)

N.B.: Dans cet exemple (Android+ESP32), seule la définition du nombre maximum de bytes dans la fonction BLEManager.write() permet d’augmenter la limite. Nous laissons les autres infos qui pourraient être utiles pour une autre configuration (iOS, HM10, etc.)

Une fois le code modifié, lorsqu’on renvoie la même chaîne de caractère, on reçoit la totalité de sa valeur dans la caractéristiques BLE

Applications

  • Augmenter la taille des messages envoyés au module BLE
  • Imiter le fonctionnement du Bluetooth classic de manière ponctuelle

Sources

Vidéo stream avec Gstreamer sur Raspberry Pi

Vidéo stream avec Gstreamer sur Raspberry Pi

Nous allons voir dans ce tutoriel comment streamer un flux vidéo à partir d’un Raspberry Pi avec Gstreamer. Un des outils de streaming les plus utilisé est FFMPEG. Nous testons ici, gstreamer car il y a moins de délai de transmission.

Précédent tutoriel de streaming : Flux vidéo entre deux machine avec FFMPEG

Matériel

  • Raspberry Pi avec Raspbian
  • USB Cam ou RPiCam

Installation de Gstreamer sur Raspberry Pi

Une partie de Gstreamer est installée par défaut sous Raspbian. Nous allons simplement compléter l’installation avec quelques librairies supplémentaires.

# install a missing dependency
$ sudo apt-get install libx264-dev libjpeg-dev
# install the remaining plugins
$ sudo apt-get install libgstreamer1.0-dev \
     libgstreamer-plugins-base1.0-dev \
     libgstreamer-plugins-bad1.0-dev \
     gstreamer1.0-plugins-ugly \
     gstreamer1.0-tools \
     gstreamer1.0-gl \
     gstreamer1.0-gtk3
# if you have Qt5 install this plugin
$ sudo apt-get install gstreamer1.0-qt5
# install if you want to work with audio
$ sudo apt-get install gstreamer1.0-pulseaudio

Pour tester l’installation, entrer la commande suivante

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

Trouver les appareils vidéo et audio disponibles

gst-device-monitor-1.0

Stream vidéo avec Gstreamer

Une fois le nom de l’appareil obtenu, vous pouvez commencer le stream avec la commande

gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw, width=640, height=480, framerate=30/1 ! videoconvert ! videoscale ! clockoverlay time-format="%D %H:%M:%S" ! autovideosink

Nous envoyons le flux vidéo avec le protocole UDP depuis un Raspberry Pi

gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=-1 ! video/x-raw, width=640, height=480, framerate=30/1 ! clockoverlay time-format="%D %H:%M:%S" ! videoconvert ! jpegenc ! udpsink host=192.168.1.70 port=5200

Nous recevons le stream UDP depuis le client (192.168.1.70) à l’aide de la commande

gst-launch-1.0 udpsrc port=5200 ! jpegdec ! videoconvert ! autovideosink

Pour enregistrer le flux vidéo dans un fichier

gst-launch-1.0 -e v4l2src device=/dev/video0 num-buffers=-1 ! 'image/jpeg,framerate=30/1,width=640,height=480' ! avimux ! filesink location=video.avi

gst-launch-1.0 -e v4l2src device=/dev/video0 num-buffers=-1 ! ‘image/jpeg,framerate=30/1,width=640,height=480’ ! queue ! avimux ! filesink location=video.avi

Installation de Gstreamer sur Windows

Pour recevoir le flux vidéo depuis le Raspberry Pi, nous utilisons un ordinateur Windows. Vous pouvez utiliser un autre appareil (RPi, Android, iOS, macOS).

Pour information, l’installation de Gstreamer sous Windows se fait à l’aide d’un installateur msi disponible sur le site.

Après avoir téléchargé et installé Gstreamer, vous devez rajouter le dossier C:\gstreamer\1.0\msvc_x86_64\bin à la variable d’environnement Path.

Pour tester l’installation, entrer la commande suivante

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

Pour streamer, une vidéo à partir de la caméra

gst-launch-1.0 mfvideosrc ! video/x-raw, width=1280, height=720, framerate=30/1 ! videoconvert ! videoscale ! clockoverlay time-format="%D %H:%M:%S" ! video/x-raw, width=640, height=360 ! autovideosink

Il est possible de lancer un stream avec le protocole UDP

gst-launch-1.0 mfvideosrc ! video/x-raw, width=1280, height=720, framerate=30/1 ! videoconvert ! videoscale ! clockoverlay time-format="%D %H:%M:%S" ! video/x-raw, width=640, height=360 ! jpegenc ! udpsink host=192.168.1.92 port=5200

Nous recevons le stream UDP depuis le client(192.168.1.92) à l’aide de la commande

gst-launch-1.0 udpsrc port=5200 ! jpegdec ! videoconvert ! autovideosink

Installation de Gstreamer sur Ubuntu ou Debian

Afin d’utiliser Gstreamer dans les meilleurs conditions nous auron besoin de la librairie v4l2

sudo apt install v4l-utils

Dans un terminal, entrée les commandes suivantes pour installer gstreamer.

sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

Pour tester l’installation, entrer la commande suivante

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

Afin de lancer le stream video depuis Ubuntu, vous pouvez lancer la commande suivante

gst-launch-1.0 v4l2src name="/dev_video0" ! decodebin ! videoconvert ! videoscale ! clockoverlay time-format="%D %H:%M:%S" ! video/x-raw,format=RGB ! queue ! videoconvert ! 

N.B.: n’oubliez pas d’ajouter decodebin

Il est possible de lancer un stream avec le protocole UDP

gst-launch-1.0 v4l2src name="/dev_video0" num-buffers=-1 ! decodebin ! videoconvert ! videoscale ! clockoverlay time-format="%D %H:%M:%S" ! video/x-raw,format=RGB ! queue ! videoconvert ! jpegenc ! udpsink host=192.168.1.70 port=5200

Nous recevons le stream UDP depuis le client (192.168.1.70) à l’aide de la commande

gst-launch-1.0 udpsrc port=5200 ! jpegdec ! videoconvert ! autovideosink

Sources

Communication UDP avec React Native

Communication UDP avec React Native

Dans ce tutoriel, nous allons mettre en place une communication avec le protocole UDP sur une application React Native. L’application React Native pourra se comporter comme Serveur ou comme Client UDP. Nous utilisons un ordinateur avec un script Python comme partenaire de communication.

Configuration de React Native

Premièrement, créer un projet React Native UDPTerminalApp

Installer les librairies udp et netinfo

npm install react-native-udp
npm install react-native-network-info

Description du code de l’application

Importation

Pour utiliser les librairies, nous importons tout d’abord leur code.

   import dgram from 'react-native-udp'
   import UdpSocket from 'react-native-udp/lib/types/UdpSocket.js';
   import { NetworkInfo } from 'react-native-network-info'

Composant principal

Puis nous créons le composant fonctionnel qui nous permettra de gérer la communication Serveur/Client UDP.

const UDPTerminal = () =>  {
  const [isServer, setIsServer] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState('');
  const [socket, setSocket] = useState<UdpSocket>();
  const [ipAddress, setIpAddress] = useState('');
  const [ipServer, setIpServer] = React.useState('');
  const [messageText, setMessageText] = useState("");
  const [messageToSend, setMessageToSend] = useState("Hello from server");
  const [receivedMessage, setReceivedMessage] = useState("");
  • isServer est True si l’application se comporte comme un serveur et false si elle se comporte comme un client
  • connectionStatus affiche le status de la connexion
  • socket contient la variable socket de la communication
  • ipAddress contient l’adresse IP de l’appareil Android sur le réseau
  • ipServer contient l’adresse IP entrée sur l’interface
  • messageText contient le texte tapé sur l’interface
  • messageToSend contient le message à envoyer au client ou au serveur
  • receivedMessage contient les messages reçu depuis le client ou le serveur

Gestion de la communication UDP

Le code qui permet la gestion de la communication UDP, que ce soit en mode serveur ou client, est entièrement contenu dans un hook useEffect

  useEffect(() => {
    const fetchIpAddress = async () => {
      const ip = await NetworkInfo.getIPV4Address();
      setIpAddress(ip);
      console.log("ip adresses ; ", ip)
    };

    fetchIpAddress();

    if (isServer) {
      // Configure app as server
      const server = dgram.createSocket('udp4');

      server.on('message', (data, rinfo) => {
        
        server.send(messageToSend, undefined, undefined, rinfo?.port, rinfo?.address, (error) => {
          if (error) {
            console.log('Error sending message:', error);
          } else {
            console.log('Message sent correctly');
          }
        });
        console.log('Received message:', data.toString());
        setReceivedMessage(receivedMessage => receivedMessage+data.toString()+"\n")
      });

      server.on('listening', () => {
        console.log('Server listening on port:', server.address().port);
        setConnectionStatus(`Server listening on port ${server.address().port}`);
      });
      try{
        setReceivedMessage("");
        server.bind(8888);
        setSocket(server);
      }catch(error){
        console.log("error binding server",error);
      }
      
    } else {
      setConnectionStatus(`Server disconnected`);
      // Configure app as client
      const client = dgram.createSocket('udp4');
      try{
        setReceivedMessage("");
        client.bind(8887);
        setSocket(client);
      }catch(error){
        console.log("error binding client",error);
      }
      
    }

    return () => {
      socket && socket.close();
    };
  }, [isServer, messageToSend, messageText]);

La fonction fetchIpAdress permet de récupérer l’adresse IP de l’appareil.

En mode serveur

  • nous créons le socket dgram.createSocket
  • lorsqu’un message est reçu: nous envoyons messageToSend au client
  • Puis nous affichons le message reçu
  • nous lions le serveur au port 8888

En mode client

  • nous créons le socket dgram.createSocket
  • nous lions le client au port 8887

Dans la fonction suivante

  • nous envoyons le message messageToSend au serveur
  • puis nous attendons la réponse que nous affichons dans receivedMessage

Fonction envoyer message

Nous définissons également une fonction qui permet de mettre à jour le message envoyé en mode Server et d’envoyer le message en mode Client.

  const sendMessage = () => {
    setMessageToSend(messageText);
    if (isServer) return;

    const client = socket;
    console.log("send message to "+ipServer)
    client.send(messageToSend, undefined, undefined, 8888, ipServer, (error) => {
      if (error) {
        console.log('Error sending message:', error);
      } else {
        console.log('Message sent correctly');
      }
    });
    client.on('message', async (message: { toString: () => string; }) => {
      setReceivedMessage(receivedMessage+message.toString()+"\n")
    });
  };

Définition de l’affichage

Enfin l’interface graphique de l’application est défini comme suit:

  • Le titre de l’application
  • L’adresse IP de l’appareil
  • Un bouton pour sélectionner le mode client ou serveur
  • Un encart permettant d’éditer le message à envoyer
  • Un bouton d’envoi
  • Un encart pour spécifier l’adresse IP du serveur en mode client
  • Une zone de texte pour afficher les messages reçu
return (
    <View style={styles.mainBody}>
      <Text
        style={styles.mainTitle}>
        AC UDP Terminal
      </Text>
      <ScrollView>

      <View style={styles.deviceItem}>
                      <View>
                        <Text style={styles.deviceName}>{ipAddress}</Text>
                        <Text style={styles.deviceInfo}>{connectionStatus}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() => setIsServer(!isServer)}
                        style={styles.deviceButton}>
                        <Text
                          style={styles.buttonText}>
                          {isServer ? 'Server' : 'Client'}
                        </Text>
                      </TouchableOpacity>
                    </View>


      <View
              style={styles.inputBar}>        
              <TextInput
                style={styles.textInput}
                placeholder="Enter a message"
                value={messageText}
                onChangeText={(text) =>    setMessageText(text)
                }
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={styles.buttonText}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>

      <TextInput
        placeholder="Server IP"
        onChangeText={setIpServer}
        value={ipServer}
      />
      <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
              style={styles.textOutput} >
                    {receivedMessage}
              </TextInput>
      </ScrollView>
    </View>
  );

N.B.: le style de l’interface est défini dans le fichier /src/styles/styles.jsx

React-Native UDP Client – Python UDP Server

Dans cet exemple, nous configurons l’application comme client et l’ordinateur comme serveur. Nous allons envoyer un message au serveur, depuis l’application, qui renverra une réponse au client.

Python UDP Server Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket

localIP     = "0.0.0.0" #"127.0.0.1"
localPort   = 8888
bufferSize  = 1024

msgFromServer       = "Hello UDP Client"
bytesToSend         = str.encode(msgFromServer)

# Create a datagram socket
UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
 
# Bind to address and ip
UDPServerSocket.bind((localIP, localPort))
print("UDP server up and listening on port {}".format(localPort))

# Listen for incoming datagrams
while(True):

	data,addr = UDPServerSocket.recvfrom(bufferSize)
	print("{} > {}".format(addr,data))

	# Sending a reply to client
	UDPServerSocket.sendto(bytesToSend, addr)

N.B.: Pour écouter sur toutes les adresses locales nous utilisons 0.0.0.0. Si vous souhaitez tester la communication entre client et serveur sur le même ordinateur vous pouvez utiliser l’adresse 127.0.0.1

Sur l’application, dans l’encart ServerIp, entrez l’adresse IP de l’ordinateur (ici: 192.168.1.70). Modifiez ensuite le message à envoyer puis appuyer sur le bouton “SEND”.

Python terminal

UDP server up and listening on port 8888
('192.168.1.5', 8887) > b'Hello server'
('192.168.1.5', 8887) > b'Hello server'

Sur le terminal Python, nous recevons le message “Hello server” envoyé depuis l’application. Le serveur UDP renvoie “Hello UDP Client”

React-Native UDP Server – Python UDP Client

Ici, nous configurons l’application comme serveur et l’ordinateur comme client. Dans le code Python, nous entrons l’adresse IP du serveur.

Python UDP Client

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import time
HOST = "192.168.1.5" #Server IP #"127.0.0.1" local address
PORT = 8888
bufferSize = 1024

counter=0
while True:
	# Create a UDP socket at client side
	with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as client:
		# Send to server using created UDP socket
		client.sendto(str.encode("client counter : "+str(counter)), (HOST,PORT))
		data,addr= client.recvfrom(bufferSize)
		msg = "{} > {}".format(addr,data)

		print(msg)
	counter+=1
	time.sleep(1)

Python terminal

('192.168.1.5', 8888) > b'Hello client '
('192.168.1.5', 8888) > b'Hello client '

Code complet de l’application

App.tsx

/**
 * npm install react-native-udp
 * npm install react-native-network-info
 * 
 */

import React, {useState, useEffect} from 'react';
import {   
  View, 
  ScrollView, 
  Text,
  TextInput,
  PermissionsAndroid,
  Platform, 
  TouchableOpacity } from 'react-native';
   import dgram from 'react-native-udp'
   import UdpSocket from 'react-native-udp/lib/types/UdpSocket.js';
   import { NetworkInfo } from 'react-native-network-info'
   import {styles} from "./src/styles/styles.jsx"


const UDPTerminal = () =>  {
  const [isServer, setIsServer] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState('');
  const [socket, setSocket] = useState<UdpSocket>();
  const [ipAddress, setIpAddress] = useState('');
  const [ipServer, setIpServer] = React.useState('');
  const [messageText, setMessageText] = useState("");
  const [messageToSend, setMessageToSend] = useState("Hello from server");
  const [receivedMessage, setReceivedMessage] = useState("");

  useEffect(() => {
    const fetchIpAddress = async () => {
      const ip = await NetworkInfo.getIPV4Address();
      setIpAddress(ip);
      console.log("ip adresses ; ", ip)
    };

    fetchIpAddress();

    if (isServer) {
      // Configure app as server
      const server = dgram.createSocket('udp4');

      server.on('message', (data, rinfo) => {
        
        server.send(messageToSend, undefined, undefined, rinfo?.port, rinfo?.address, (error) => {
          if (error) {
            console.log('Error sending message:', error);
          } else {
            console.log('Message sent correctly');
          }
        });
        console.log('Received message:', data.toString());
		if(receivedMessage.length>300){
          setReceivedMessage("");
        }
        setReceivedMessage(receivedMessage => receivedMessage+data.toString()+"\n")
      });

      server.on('listening', () => {
        console.log('Server listening on port:', server.address().port);
        setConnectionStatus(`Server listening on port ${server.address().port}`);
      });
      try{
        setReceivedMessage("");
        server.bind(8888);
        setSocket(server);
      }catch(error){
        console.log("error binding server",error);
      }
      
    } else {
      setConnectionStatus(`Server disconnected`);
      // Configure app as client
      const client = dgram.createSocket('udp4');
      try{
        setReceivedMessage("");
        client.bind(8887);
        setSocket(client);
      }catch(error){
        console.log("error binding client",error);
      }
      
    }

    return () => {
      socket && socket.close();
    };
  }, [isServer, messageToSend, messageText]);

  const sendMessage = () => {
    setMessageToSend(messageText);
    if (isServer) return;

    const client = socket;
    console.log("send message to "+ipServer)
    client.send(messageToSend, undefined, undefined, 8888, ipServer, (error) => {
      if (error) {
        console.log('Error sending message:', error);
      } else {
        console.log('Message sent correctly');
      }
    });
    client.on('message', async (message: { toString: () => string; }) => {
	  if(receivedMessage.length>300){
          setReceivedMessage("");
      }
      setReceivedMessage(receivedMessage+message.toString()+"\n")
    });
  };

  return (
    <View style={styles.mainBody}>
      <Text
        style={styles.mainTitle}>
        AC UDP Terminal
      </Text>
      <ScrollView>

      <View style={styles.deviceItem}>
                      <View>
                        <Text style={styles.deviceName}>{ipAddress}</Text>
                        <Text style={styles.deviceInfo}>{connectionStatus}</Text>
                      </View>
                      <TouchableOpacity
                        onPress={() => setIsServer(!isServer)}
                        style={styles.deviceButton}>
                        <Text
                          style={styles.buttonText}>
                          {isServer ? 'Server' : 'Client'}
                        </Text>
                      </TouchableOpacity>
                    </View>


      <View
              style={styles.inputBar}>        
              <TextInput
                style={styles.textInput}
                placeholder="Enter a message"
                value={messageText}
                onChangeText={(text) =>    setMessageText(text)
                }
              />
              <TouchableOpacity
                        onPress={() => sendMessage()
                        }
                        style={[styles.sendButton]}>
                        <Text
                          style={styles.buttonText}>
                          SEND
                        </Text>
                      </TouchableOpacity>
        </View>

      <TextInput
        placeholder="Server IP"
        onChangeText={setIpServer}
        value={ipServer}
      />
      <Text>Received Message:</Text>
              <TextInput
              editable = {false}
              multiline
              numberOfLines={20}
              maxLength={300}
              style={styles.textOutput} >
                    {receivedMessage}
              </TextInput>
      </ScrollView>
    </View>
  );
}

export default UDPTerminal;

style.jsx

/* ./src/styles/styles.jsx */
import {StyleSheet, Dimensions} from 'react-native';
/**
 * links to palette
 *  https://colorhunt.co/palette/161616346751c84b31ecdbba
 * 
*/
//parameters
let BACKGROUND_COLOR = "#161616"; //191A19
let BUTTON_COLOR = "#346751"; //1E5128
let ERROR_COLOR = "#C84B31"; //4E9F3D
let TEXT_COLOR = "#ECDBBA"; //D8E9A8

const windowHeight = Dimensions.get('window').height;
export const styles = StyleSheet.create({
  mainBody: {
    flex: 1,
    justifyContent: 'center',
    height: windowHeight,
    color:TEXT_COLOR,
    backgroundColor: BACKGROUND_COLOR,
  },

  mainTitle:{
    color: TEXT_COLOR,
    fontSize: 30,
    textAlign: 'center',
    borderBottomWidth: 2,
    borderBottomColor: ERROR_COLOR,
  },

  scanButton: {
    backgroundColor: BUTTON_COLOR,
    paddingBottom: 10,
    paddingTop: 10,
    borderRadius: 10,
    margin: 2,
    marginBottom: 10,
    marginTop: 10,
    
    paddingHorizontal: 20,
    fontWeight: 'bold', 
    fontSize: 12,
  },

  buttonText: {
    color: TEXT_COLOR,
    fontWeight: 'bold',
    fontSize: 12,
	  textAlign: 'center',
  },
  
  noDevicesText: {
    color: TEXT_COLOR,
    textAlign: 'center',
    marginTop: 10,
    fontStyle: 'italic',
  },
  deviceItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 2,
  },
  deviceName: {
    color: TEXT_COLOR,
    fontSize: 14,
    fontWeight: 'bold',
  },
  deviceInfo: {
    color: TEXT_COLOR,
    fontSize: 10,
  },
  deviceButton: {
    backgroundColor: BUTTON_COLOR,
    padding: 10,
    borderRadius: 10,
    margin: 2,
    paddingHorizontal: 20,
    fontWeight: 'bold', 
    fontSize: 12,
  },

  inputBar:{
    flexDirection: 'row',
    justifyContent: 'space-between',
    margin: 5,
  },

  textInput:{
    backgroundColor: '#888888',
    margin: 2,
    borderRadius: 15,
    flex:3,
  },

  sendButton: {
  backgroundColor: BUTTON_COLOR,
  padding: 15,
  borderRadius: 15,
  margin: 2,
  paddingHorizontal: 20,
  },

  textOutput:{
    backgroundColor: '#333333',
    margin: 10,
    borderRadius: 2,
    borderWidth: 1,
    borderColor: '#EEEEEE',
    textAlignVertical: 'top',
  }

});

Applications

  • Pilotez votre projet Arduino, ESP32 ou Raspberry Pi avec une application

Sources