Différences
Ci-dessous, les différences entre deux révisions de la page.
Les deux révisions précédentes Révision précédente Prochaine révision | Révision précédente | ||
tutos:mqtt [2018/10/25 07:26] – ygangat | tutos:mqtt [2020/11/02 11:45] (Version actuelle) – [Raspberry Pi : Installation du broker] mdelsaut | ||
---|---|---|---|
Ligne 1: | Ligne 1: | ||
====== MQTT (mosquitto) ====== | ====== MQTT (mosquitto) ====== | ||
- | A faire | + | Fait : |
- [x] Comparatif | - [x] Comparatif | ||
- [x] MQTT sur Raspberry Pi | - [x] MQTT sur Raspberry Pi | ||
- [x] MQTT sur Arduino | - [x] MQTT sur Arduino | ||
- [x] Relay + Capteur courant | - [x] Relay + Capteur courant | ||
- | - [x] MQTT et Node-Red | + | - [x] MQTT et Node-Red |
- | - [ ] MQTT & InfluxDB | + | - [X] MQTT & InfluxDB |
+ | | ||
+ | <WRAP center round important 60%> | ||
+ | L' | ||
+ | </ | ||
//(Note : ces informations ont été compilées à partir de différentes sources anglaises/ | //(Note : ces informations ont été compilées à partir de différentes sources anglaises/ | ||
Ligne 16: | Ligne 19: | ||
Il existe deux types d' | Il existe deux types d' | ||
- | - **Request/ | + | - **Request/ |
- | - **Publish/ | + | - **Publish/ |
Avantages et Inconvénients : [[https:// | Avantages et Inconvénients : [[https:// | ||
Ligne 30: | Ligne 33: | ||
**Points clefs**: | **Points clefs**: | ||
- | - Request/ | + | - Request/ |
- | - UDP/IP Based | + | - UDP/IP Based |
- | - QoS Support | + | - QoS Support |
- | - Supports Unicast as well as Multicast | + | - Supports Unicast as well as Multicast |
- | - Uses DTLS for Security | + | - Uses DTLS for Security |
- | - Supports Resource Discovery | + | - Supports Resource Discovery |
- | - CoAP Node plays Server Role too (NAT Issues) | + | - CoAP Node plays Server Role too (NAT Issues) |
- | - Decentralised (No Single Point of Failure) | + | - Decentralised (No Single Point of Failure) |
- | - dedsdf | + | |
=== REST/HTTP === | === REST/HTTP === | ||
Ligne 49: | Ligne 51: | ||
**Points clés**: | **Points clés**: | ||
- | - Request/ | + | - Request/ |
- | - TCP/IP Based | + | - TCP/IP Based |
- | - No QoS Support | + | - No QoS Support |
- | - Complex Implementation at Client Side | + | - Complex Implementation at Client Side |
- | - Larger Header compared to other IoT Protocols (Higher Bandwidth Requirement) | + | - Larger Header compared to other IoT Protocols (Higher Bandwidth Requirement) |
- | - Uses SSL/TLS for Security | + | - Uses SSL/TLS for Security |
==== Publish/ | ==== Publish/ | ||
Ligne 65: | Ligne 67: | ||
MQTT offre une Qualité de service (QoS) à 3 niveaux : | MQTT offre une Qualité de service (QoS) à 3 niveaux : | ||
- | - **QoS 0** : transmission de message sans accusé de réception | + | - **QoS 0** : transmission de message sans accusé de réception |
- | - **QoS 1** : transmission de message avec accusé de réception | + | - **QoS 1** : transmission de message avec accusé de réception |
- | - **QoS 2** : transmission de message avec accusé de réception et vérification du message transmis afin d’éviter qu’un même message ne soit pas transmis de nombreuses fois. | + | - **QoS 2** : transmission de message avec accusé de réception et vérification du message transmis afin d’éviter qu’un même message ne soit pas transmis de nombreuses fois. |
MQTT-SN (MQTT for Sensor Network) est une version optimisée pour les objets à très faibles ressources communiquant sans fil. | MQTT-SN (MQTT for Sensor Network) est une version optimisée pour les objets à très faibles ressources communiquant sans fil. | ||
**Points clés** : | **Points clés** : | ||
- | - Publish/ | + | - Publish/ |
- | - Light Weight (Min Header Size: 2 Bytes) | + | - Light Weight (Min Header Size: 2 Bytes) |
- | - TCP/IP Based | + | - TCP/IP Based |
- | - QoS Support | + | - QoS Support |
- | - Payload Agnostic | + | - Payload Agnostic |
- | - Uses SSL/TLS for Security | + | - Uses SSL/TLS for Security |
- | - Broker could be Single Point of Failure | + | - Broker could be Single Point of Failure |
=== XMPP (Extensible Messaging and Presence Protocol) === | === XMPP (Extensible Messaging and Presence Protocol) === | ||
Ligne 96: | Ligne 98: | ||
**Points clés** : | **Points clés** : | ||
- | - Point to Point Message Exchange (Server to Server) | + | - Point to Point Message Exchange (Server to Server) |
- | - Flexible Messaging Patterns | + | - Flexible Messaging Patterns |
- | - TCP/IP Based | + | - TCP/IP Based |
- | - Smallest Packet Size: 60 Bytes | + | - Smallest Packet Size: 60 Bytes |
- | - QoS Support | + | - QoS Support |
- | - Uses SSL/TLS for Security | + | - Uses SSL/TLS for Security |
==== Autres types de communications ==== | ==== Autres types de communications ==== | ||
Ligne 110: | Ligne 112: | ||
**Points clés** : | **Points clés** : | ||
- | - Publish/ | + | - Publish/ |
- | - Relational data modeling | + | - Relational data modeling |
- | - QoS Support | + | - QoS Support |
- | - Multicast support over plain UDP sockets | + | - Multicast support over plain UDP sockets |
- | - Leverages both TCP/IP and UDP/IP Transport | + | - Leverages both TCP/IP and UDP/IP Transport |
- | - Uses TLS and DTLS for Security | + | - Uses TLS and DTLS for Security |
- | - Decentralised (No Single Point of Failure) | + | - Decentralised (No Single Point of Failure) |
+ | |||
+ | ==== Résumé ==== | ||
+ | |||
+ | {{https:// | ||
===== Le choix ===== | ===== Le choix ===== | ||
Ligne 125: | Ligne 131: | ||
Pour rappel, dans un système MQTT complet nous retrouvons quatre notions importantes : | Pour rappel, dans un système MQTT complet nous retrouvons quatre notions importantes : | ||
- | - Les publishers : L’origine des messages | + | - Les publishers : L’origine des messages |
- | - Les subscribers : La destination des messages | + | - Les subscribers : La destination des messages |
- | - Le broker : L’élément qui fait le lien entre les publishers et les subscribers | + | - Le broker : L’élément qui fait le lien entre les publishers et les subscribers |
- | - Les topics : Canaux de communications. | + | - Les topics : Canaux de communications. |
> Message Queue Telemetry Transport, MQTT pour abréger, est un protocole de messagerie utile dans la communication entre les dispositifs de l' | > Message Queue Telemetry Transport, MQTT pour abréger, est un protocole de messagerie utile dans la communication entre les dispositifs de l' | ||
Ligne 163: | Ligne 169: | ||
Pour une configuration " | Pour une configuration " | ||
- | - **Activer la connexion SSH** en créant un fichier '' | + | - **Activer la connexion SSH** en créant un fichier '' |
- | - **Créer un fichier '' | + | - **Créer un fichier '' |
< | < | ||
Ligne 177: | Ligne 183: | ||
} | } | ||
</ | </ | ||
- | < | + | Remarque : |
- | < | + | |
- | < | + | |
< | < | ||
interface wlan0 | interface wlan0 | ||
Ligne 185: | Ligne 192: | ||
static routers=192.168.1.1 | static routers=192.168.1.1 | ||
static domain_name_servers=192.168.1.1 | static domain_name_servers=192.168.1.1 | ||
- | </code>< | + | </ |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | - remove-home pi%%'' | + | -remove-home pi%%'' |
- | - -lock pi%%'' | + | --lock pi%%'' |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
- | < | + | |
==== Raspberry Pi : Installation du broker ==== | ==== Raspberry Pi : Installation du broker ==== | ||
+ | |||
+ | L' | ||
< | < | ||
Ligne 218: | Ligne 227: | ||
< | < | ||
< | < | ||
- | - c / | + | -c / |
< | < | ||
< | < | ||
allow_anonymous false | allow_anonymous false | ||
password_file / | password_file / | ||
- | </ | + | </ |
- | < | + | Ensuite il faut faire : < |
- | < | + | < |
+ | Faire '' | ||
< | < | ||
Nous interdisons l’accès en anonyme et nous allons autoriser les connexions uniquement avec un identifiant mot de passe (dont la liste se trouve le fichier '' | Nous interdisons l’accès en anonyme et nous allons autoriser les connexions uniquement avec un identifiant mot de passe (dont la liste se trouve le fichier '' | ||
+ | |||
+ | // | ||
+ | -b passwordfile user password%%'' | ||
* Pour tester la connexion depuis un autre appareil, il est possible d' | * Pour tester la connexion depuis un autre appareil, il est possible d' | ||
Ligne 244: | Ligne 257: | ||
Lors du débogage de la communication avec un serveur MQTT, il peut s' | Lors du débogage de la communication avec un serveur MQTT, il peut s' | ||
- | - h 192.168.0.22 | + | -h 192.168.0.22 |
- | - v | + | -v |
- | - t "#" | + | -t "#" |
==== Arduino : Matériel ==== | ==== Arduino : Matériel ==== | ||
Dans notre cas, nous avons utilisé : | Dans notre cas, nous avons utilisé : | ||
- | - Un [[https:// | + | - Un [[https:// |
- | - Un [[https:// | + | - Un [[https:// |
- | - Un [[http:// | + | - Un [[http:// |
- | - LCD RGB Backlight]] | + | - LCD RGB Backlight]] |
- | - Un [[https:// | + | - Un [[https:// |
Le //Grove | Le //Grove | ||
- | - LCD RGB Backlight// est branché sur le 1er port //I2C// du //Grove Base Shield//. Ce dernier (mis sous 5V) a été placé entre le //WiFi Shield// et l'// | + | - LCD RGB Backlight// est branché sur le 1er port //I2C// du //Grove Base Shield//. Ce dernier (mis sous 5V) a été placé entre le //WiFi Shield// et l'// |
- | L'// | + | L'// |
+ | - à l’extérieur). | ||
Les //Adafruit ATWINC1500 WiFi Shield// ont été mis à jour avec le dernier firmware (Voir [[https:// | Les //Adafruit ATWINC1500 WiFi Shield// ont été mis à jour avec le dernier firmware (Voir [[https:// | ||
Ligne 269: | Ligne 283: | ||
Il est important de bien configurer l'IDE et d' | Il est important de bien configurer l'IDE et d' | ||
- | - Librairies : | + | - Librairies : |
- | - WiFi101 | + | - WiFi101 |
- | - Adafruit IO Arduino | + | - Adafruit IO Arduino |
- | - Grove | + | - Grove |
- | - LCD RGB Backlight | + | - LCD RGB Backlight |
- | - Pubsubclient //<- non utilisé// | + | - Pubsubclient //<- non utilisé// |
- | - Configuration : | + | - Configuration : |
- | - Type de carte : **Arduino/ | + | - Type de carte : **Arduino/ |
- | - Serial : **9600 baud** | + | - Serial : **9600 baud** |
==== Arduino : Code ==== | ==== Arduino : Code ==== | ||
Le déroulement du programme est le suivant : | Le déroulement du programme est le suivant : | ||
- | - Imports & Initialisations | + | - Imports & Initialisations |
- | - Connexion au WiFi | + | - Connexion au Wifi |
- | - Inscription au flux '' | + | - Inscription au flux '' |
- | - Boucle : | + | - Boucle : |
- | - | + | - Connexion |
- | - Ecoute au flux '' | + | - Ecoute au flux '' |
- | - Publication d'un entier (croissant au fur et à mesure) sur le flux '' | + | - Publication d'un entier (croissant au fur et à mesure) sur le flux '' |
Le code du programme '' | Le code du programme '' | ||
Ligne 307: | Ligne 321: | ||
// | // | ||
- | - --------------------------- GROVE LCD Lib | + | ---------------------------- GROVE LCD Lib |
- | - ------------------------ | + | ------------------------- |
//#include < | //#include < | ||
#include " | #include " | ||
Ligne 348: | Ligne 362: | ||
// | // | ||
- | - -------------------------- Variables & others | + | --------------------------- Variables & others |
- | - ------------------------------- | + | -------------------------------- |
rgb_lcd lcd; | rgb_lcd lcd; | ||
byte heart[8] = { | byte heart[8] = { | ||
Ligne 364: | Ligne 378: | ||
void setup() { | void setup() { | ||
// | // | ||
- | - ------------------------ Initialization Serial | + | ------------------------- Initialization Serial |
- | - ----------------------------- | + | ------------------------------ |
while (!Serial); | while (!Serial); | ||
Serial.begin(9600); | Serial.begin(9600); | ||
Ligne 372: | Ligne 386: | ||
// | // | ||
- | - --------------------- Initialization Wifi WINC1500 | + | ---------------------- Initialization Wifi WINC1500 |
- | - ------------------------- | + | -------------------------- |
// Initialise the Client | // Initialise the Client | ||
Serial.print(F(" | Serial.print(F(" | ||
Ligne 385: | Ligne 399: | ||
// | // | ||
- | - ----------------------------- Physical part | + | ------------------------------ Physical part |
- | - --------------------------------- | + | ---------------------------------- |
Serial.println(F(" | Serial.println(F(" | ||
Ligne 403: | Ligne 417: | ||
// | // | ||
- | - -------------------------- Variables & others | + | --------------------------- Variables & others |
- | - ------------------------------- | + | -------------------------------- |
mqtt.subscribe(& | mqtt.subscribe(& | ||
} | } | ||
Ligne 412: | Ligne 426: | ||
void loop() { | void loop() { | ||
// | // | ||
- | - ----------------------------- MQTT Connection | + | ------------------------------ MQTT Connection |
- | - ------------------------------- | + | -------------------------------- |
// Ensure the connection to the MQTT server is alive (this will make the first | // Ensure the connection to the MQTT server is alive (this will make the first | ||
// connection and automatically reconnect when disconnected). | // connection and automatically reconnect when disconnected). | ||
Ligne 419: | Ligne 433: | ||
lcd.setRGB(255, | lcd.setRGB(255, | ||
// | // | ||
- | - ----------------------------- Subscription | + | ------------------------------ Subscription |
- | - ---------------------------------- | + | ----------------------------------- |
Adafruit_MQTT_Subscribe *subscription; | Adafruit_MQTT_Subscribe *subscription; | ||
while ((subscription = mqtt.readSubscription(5000))) { | while ((subscription = mqtt.readSubscription(5000))) { | ||
Ligne 441: | Ligne 455: | ||
// | // | ||
- | - ----------------------------- Publication | + | ------------------------------ Publication |
- | - --------------------------------- | + | ---------------------------------- |
Serial.print(F(" | Serial.print(F(" | ||
Serial.print(x); | Serial.print(x); | ||
Ligne 497: | Ligne 511: | ||
</ | </ | ||
Remarques sur ce code : | Remarques sur ce code : | ||
- | - Le '' | + | - Le '' |
- | - Le mot de passe du wifi est stocké dans '' | + | - Le mot de passe du wifi est stocké dans '' |
- | - Dès que la mémoire dynamique (vive) est utilisé | + | - Dès que la mémoire dynamique (vive) est utilisée |
- | - La solution la plus rapide pour diminuer ce nombre a été de remplacer '' | + | - La solution la plus rapide pour diminuer ce nombre a été de remplacer '' |
- | - Une autre solution est de mettre '' | + | - Une autre solution est de mettre '' |
- | - Une solution plus complexe mais envisageable à la fin est d' | + | - Une solution plus complexe, mais envisageable à la fin est d' |
- | - Sachant que les '' | + | - Sachant que les '' |
- | - de le supprimer lorsque tout fonctionne | + | - de le supprimer lorsque tout fonctionne |
- | - d' | + | - d' |
<code cpp> | <code cpp> | ||
Ligne 517: | Ligne 531: | ||
#endif | #endif | ||
</ | </ | ||
- | Plus d' | + | Plus d' |
- | - | + | - En français sur [[https:// |
- | - | + | - En anglais sur [[https:// |
+ | Le fichier '' | ||
+ | |||
+ | <code c> | ||
+ | #define SECRET_SSID " | ||
+ | #define SECRET_PASS " | ||
+ | </ | ||
===== Application ===== | ===== Application ===== | ||
Ligne 526: | Ligne 546: | ||
Pour la suite du test, nous avons fait le montage suivant sur le Arduino : | Pour la suite du test, nous avons fait le montage suivant sur le Arduino : | ||
- | - Nous avons branché un [[https:// | + | - Nous avons branché un [[https:// |
- | {{http:// | + | {{http:// |
- | * Nous avons utilisé un [[http:// | + | * Nous avons utilisé un [[http:// |
- | {{https:// | + | {{https:// |
<code c> | <code c> | ||
Ligne 548: | Ligne 568: | ||
digitalWrite(RELAY1, | digitalWrite(RELAY1, | ||
Serial.println(" | Serial.println(" | ||
- | - ---------------------" | + | ----------------------" |
delay(5000); | delay(5000); | ||
mesure(analogInPin); | mesure(analogInPin); | ||
Ligne 555: | Ligne 575: | ||
digitalWrite(RELAY1, | digitalWrite(RELAY1, | ||
Serial.println(" | Serial.println(" | ||
- | - ----------------------" | + | -----------------------" |
delay(2000); | delay(2000); | ||
mesure(analogInPin); | mesure(analogInPin); | ||
Ligne 573: | Ligne 593: | ||
Serial.println(sensorValue); | Serial.println(sensorValue); | ||
sensorValue = ((sensorValue | sensorValue = ((sensorValue | ||
- | - 506) * 5 / 1024 / 0.066); | + | - 506) * 5 / 1024 / 0.066); |
// print the results to the serial monitor: | // print the results to the serial monitor: | ||
Serial.print(" | Serial.print(" | ||
Ligne 585: | Ligne 605: | ||
Ensuite nous avons modifié le code original de notre Arduino (voir [[# | Ensuite nous avons modifié le code original de notre Arduino (voir [[# | ||
- | - Le relay s' | + | - Le relai s' |
- | - Le capteur permet de mesurer le courant que nous ferons varier avec un potentiomètre. L' | + | - Le capteur permet de mesurer le courant que nous ferons varier avec un potentiomètre. L' |
Nous avons donc le code suivant : | Nous avons donc le code suivant : | ||
Ligne 605: | Ligne 625: | ||
// | // | ||
- | - --------------------------- GROVE LCD Lib | + | ---------------------------- GROVE LCD Lib |
- | - ------------------------ | + | ------------------------- |
//#include < | //#include < | ||
#include " | #include " | ||
Ligne 652: | Ligne 672: | ||
// | // | ||
- | - -------------------------- Variables & others | + | --------------------------- Variables & others |
- | - ------------------------------- | + | -------------------------------- |
rgb_lcd lcd; | rgb_lcd lcd; | ||
byte heart[8] = { | byte heart[8] = { | ||
Ligne 668: | Ligne 688: | ||
void setup() { | void setup() { | ||
// | // | ||
- | - ------------------------ Initialization Serial | + | ------------------------- Initialization Serial |
- | - ----------------------------- | + | ------------------------------ |
while (!Serial); | while (!Serial); | ||
Serial.begin(9600); | Serial.begin(9600); | ||
Ligne 676: | Ligne 696: | ||
// | // | ||
- | - --------------------- Initialization Wifi WINC1500 | + | ---------------------- Initialization Wifi WINC1500 |
- | - ------------------------- | + | -------------------------- |
// Initialise the Client | // Initialise the Client | ||
Serial.print(F(" | Serial.print(F(" | ||
Ligne 689: | Ligne 709: | ||
// | // | ||
- | - ----------------------------- Physical part | + | ------------------------------ Physical part |
- | - --------------------------------- | + | ---------------------------------- |
Serial.println(F(" | Serial.println(F(" | ||
Ligne 709: | Ligne 729: | ||
// | // | ||
- | - -------------------------- Variables & others | + | --------------------------- Variables & others |
- | - ------------------------------- | + | -------------------------------- |
mqtt.subscribe(& | mqtt.subscribe(& | ||
} | } | ||
Ligne 718: | Ligne 738: | ||
void loop() { | void loop() { | ||
// | // | ||
- | - ----------------------------- MQTT Connection | + | ------------------------------ MQTT Connection |
- | - ------------------------------- | + | -------------------------------- |
// Ensure the connection to the MQTT server is alive (this will make the first | // Ensure the connection to the MQTT server is alive (this will make the first | ||
// connection and automatically reconnect when disconnected). | // connection and automatically reconnect when disconnected). | ||
Ligne 725: | Ligne 745: | ||
lcd.setRGB(255, | lcd.setRGB(255, | ||
// | // | ||
- | - ----------------------------- Subscription | + | ------------------------------ Subscription |
- | - ---------------------------------- | + | ----------------------------------- |
Adafruit_MQTT_Subscribe *subscription; | Adafruit_MQTT_Subscribe *subscription; | ||
while ((subscription = mqtt.readSubscription(5000))) { | while ((subscription = mqtt.readSubscription(5000))) { | ||
Ligne 749: | Ligne 769: | ||
// | // | ||
- | - ----------------------------- Publication | + | ------------------------------ Publication |
- | - --------------------------------- | + | ---------------------------------- |
Serial.print(F(" | Serial.print(F(" | ||
Ligne 811: | Ligne 831: | ||
Voltage = (RawValue / 1024.0) * 5000; // Gets you mV | Voltage = (RawValue / 1024.0) * 5000; // Gets you mV | ||
Amps = ((Voltage | Amps = ((Voltage | ||
- | - ACSoffset) / mVperAmp); | + | - ACSoffset) / mVperAmp); |
/ | / | ||
Serial.print(RawValue); | Serial.print(RawValue); | ||
Ligne 826: | Ligne 846: | ||
</ | </ | ||
Remarque : | Remarque : | ||
- | - on est toujours confronté au problème de mémoire dynamique. Dans les logs de téléversement de Arduino, si dans la phrase '' | + | - on est toujours confronté au problème de mémoire dynamique. Dans les logs de téléversement de Arduino, si dans la phrase '' |
====== Bonus(es) ====== | ====== Bonus(es) ====== | ||
Ligne 834: | Ligne 854: | ||
Il est possible d' | Il est possible d' | ||
- | ===== Mosquitto | + | ===== MQTT et Node-RED ===== |
- | Node-RED est un outil puissant pour construire des applications de l' | + | Node-RED est un outil puissant pour construire des applications de l' |
La vocation de cette solution est de permettre de lier aisément des sources de données à des composants de traitement, locaux ou distants, et de créer des chaines de valeurs en quelques clics. | La vocation de cette solution est de permettre de lier aisément des sources de données à des composants de traitement, locaux ou distants, et de créer des chaines de valeurs en quelques clics. | ||
- | Node-RED est compatible avec MQTT : [[https:// | + | Node-RED est compatible avec MQTT : [[https:// |
- | ===== Installation Node-RED | + | ==== Installation Node-RED ==== |
Sur le raspberry-pi, | Sur le raspberry-pi, | ||
- | - > Preferences | + | -> Preferences |
- | - > Recommended Software" | + | -> Recommended Software" |
Si déjà installé, il est possible (voire préférable) de faire un '' | Si déjà installé, il est possible (voire préférable) de faire un '' | ||
- | Node-red peut fonctionner tel quel, cependant, pour pouvoir rajouter des modules, il faut avoir '' | + | Node-RED peut fonctionner tel quel, cependant, pour pouvoir rajouter des modules, il faut avoir '' |
- | - g%%'' | + | -g%%'' |
Une autre possibilité est de lancer la commande suivante, qui permet de faire tout ce qu'il y a à faire : '' | Une autre possibilité est de lancer la commande suivante, qui permet de faire tout ce qu'il y a à faire : '' | ||
- | - sL https:// | + | -sL https:// |
+ | |||
+ | {{https:// | ||
+ | |||
+ | ==== Démarrage ==== | ||
+ | |||
+ | Pour démarrer Node-RED dans **" | ||
+ | -> Programmation | ||
+ | -> Node-RED" | ||
+ | |||
+ | Remarque : | ||
+ | - Pour permettre d' | ||
+ | - On accéde à Node-RED avec http: | ||
+ | |||
+ | {{https:// | ||
+ | |||
+ | ==== Module Dashboard ==== | ||
+ | |||
+ | === Installation du module === | ||
+ | |||
+ | Pour contrôler nos objets MQTT, nous allons utiliser le module Dashboard. Pour cela: | ||
+ | - Aller dans le menu de Node-RED en haut à droite | ||
+ | - Cliquer sur //" | ||
+ | - Dans l' | ||
+ | |||
+ | === Configuration du module === | ||
+ | |||
+ | * Aller dans le menu de Node-RED en haut à droite | ||
+ | * Cliquer sur //" | ||
+ | -> Dashboard"// | ||
+ | * Cliquer sur **+tab** pour rajouter un écran, dans lequel on mette ensuite 2 groupes avec **+group** Note : On accéde à Node-RED avec http: | ||
+ | |||
+ | === Création de Nodes === | ||
+ | |||
+ | Une fois les nodes créés et liés, en cliquant sur **" | ||
+ | |||
+ | == Nodes MQTT == | ||
+ | |||
+ | * Faire un drag & drop d'un node MQTT **A** en input | ||
+ | * Le configurer : | ||
+ | * Configurer le serveur la première fois | ||
+ | * Configurer le topic %%// | ||
+ | * Faire un drag & drop d'un node MQTT **B** en output | ||
+ | * Le configurer : | ||
+ | * Résutiliser le même serveur | ||
+ | * Configurer le topic %%// | ||
+ | |||
+ | == Nodes Dashboard == | ||
+ | |||
+ | * Faire un drag & drop d'un node switch (input du dashboard) | ||
+ | * Le connecter avec l' | ||
+ | * Le configurer | ||
+ | * Mettre **ON** pour %%// | ||
+ | * Mettre **OFF** pour %%// | ||
+ | * Faire un drag & drop d'un node text (output du dashboard) | ||
+ | * Le connecter avec la sortie du node **A** | ||
+ | * Le configurer (on peut laisser ce qu'il y a par défaut) | ||
+ | * Faire un drag & drop d'un node chart (output du dashboard) | ||
+ | * Le connecter avec la sortie du node **A** | ||
+ | * Le configurer (on peut laisser ce qu'il y a par défaut) | ||
+ | |||
+ | == Pour test: == | ||
+ | |||
+ | Pour aller plus rapidement, il est possible de faire directement un import du code json du flow au lieu de créer les nodes précédents : | ||
+ | |||
+ | < | ||
+ | [ | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | ], | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [], | ||
+ | [] | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | </ | ||
+ | ===== MQTT et InfluxDB ===== | ||
+ | |||
+ | En partant du principe que InfluxDB a été installé (voir [[https:// | ||
+ | |||
+ | <code bash> | ||
+ | [[inputs.mqtt_consumer]] | ||
+ | ## MQTT broker URLs to be used. The format should be scheme:// | ||
+ | ## schema can be tcp, ssl, or ws. | ||
+ | | ||
+ | |||
+ | ## MQTT QoS, must be 0, 1, or 2 | ||
+ | qos = 0 | ||
+ | ## Connection timeout for initial connection in seconds | ||
+ | | ||
+ | |||
+ | ## Topics to subscribe to | ||
+ | | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | |||
+ | # if true, messages that can't be delivered while the subscriber is offline | ||
+ | # will be delivered when it comes back (such as on service restart). | ||
+ | # NOTE: if true, client_id MUST be set | ||
+ | | ||
+ | # If empty, a random client ID will be generated. | ||
+ | | ||
+ | |||
+ | ## username and password to connect MQTT server. | ||
+ | | ||
+ | | ||
+ | |||
+ | ## Optional SSL Config | ||
+ | # ssl_ca = "/ | ||
+ | # ssl_cert = "/ | ||
+ | # ssl_key = "/ | ||
+ | ## Use SSL but skip chain & host verification | ||
+ | # insecure_skip_verify = false | ||
+ | |||
+ | ## Data format to consume. | ||
+ | ## Each data format has its own unique set of configuration options, read | ||
+ | ## more about them here: | ||
+ | ## https:// | ||
+ | | ||
+ | | ||
+ | |||
+ | </ | ||
+ | Les valeurs sont ensuite stocké avec les tag appropriés (par exemple '' | ||
+ | |||
+ | //Exemple local de dashboard : http: | ||
+ | |||
+ | ===== MQTT et Java ===== | ||
+ | |||
+ | L' | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | On utilisera les variables suivantes : | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | | ||
+ | int qos = 2; | ||
+ | | ||
+ | </ | ||
+ | La création de la connexion au broker se fait comme suit : | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | On peut rajouter des options de connexions comme suit : | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | puis on se connecte avec : | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | Pour publier un message, il suffit de faire : | ||
+ | |||
+ | <code java> | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | </ | ||
+ | Pour souscrire à un topic, il faut créer une classe qui reçoit et traite les messages : | ||
+ | |||
+ | <code java> | ||
+ | // Latch used for synchronizing b/w threads | ||
+ | final CountDownLatch latch = new CountDownLatch(1); | ||
+ | |||
+ | // Topic filter the client will subscribe to | ||
+ | final String subTopic = " | ||
+ | |||
+ | // Callback | ||
+ | - Anonymous inner-class for receiving messages | ||
+ | | ||
+ | |||
+ | | ||
+ | // Called when a message arrives from the server that | ||
+ | // matches any subscription made by the client | ||
+ | | ||
+ | | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | | ||
+ | } | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | |||
+ | | ||
+ | } | ||
+ | |||
+ | }); | ||
+ | |||
+ | // Subscribe client to the topic filter and a QoS level of 0 | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | // Wait for the message to be received | ||
+ | try { | ||
+ | | ||
+ | } catch (InterruptedException e) { | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | Un [[https:// | ||
+ | |||
+ | //Source : https: | ||
+ | |||
+ | ====== Code final ====== | ||
+ | |||
+ | ===== Arduino ===== | ||
+ | |||
+ | <code c> | ||
+ | / | ||
+ | Adafruit MQTT Library WINC1500 Modified by Yassine | ||
+ | |||
+ | Demo written by Limor Fried/ | ||
+ | Modified by Yassine Gangat for LE²P. | ||
+ | |||
+ | MIT license, all text above must be included in any redistribution | ||
+ | | ||
+ | #include < | ||
+ | #include " | ||
+ | #include " | ||
+ | #include < | ||
+ | |||
+ | // | ||
+ | ---------------------------- GROVE LCD Lib | ||
+ | ------------------------- | ||
+ | //#include < | ||
+ | #include " | ||
+ | |||
+ | #include " | ||
+ | /////// | ||
+ | const char ssid[] = SECRET_SSID; | ||
+ | const char pass[] = SECRET_PASS; | ||
+ | int status = WL_IDLE_STATUS; | ||
+ | // int keyIndex = 0; // your network key Index number (needed only for WEP) | ||
+ | |||
+ | // Initialise the Arduino data pins for INPUT/ | ||
+ | const int analogInPin = A0; | ||
+ | const int RELAY2 = 8; | ||
+ | const int mVperAmp = 185; // use 100 for 20A Module and 66 for 30A Module | ||
+ | |||
+ | |||
+ | / | ||
+ | |||
+ | #define SERVER | ||
+ | #define SERVERPORT | ||
+ | #define USERNAME | ||
+ | - CtrlLoad | ||
+ | - SolarPan | ||
+ | #define PWD " | ||
+ | |||
+ | / | ||
+ | |||
+ | //Set up the wifi client & Adafruit MQTT Client | ||
+ | WiFiClient client; | ||
+ | Adafruit_MQTT_Client mqtt(& | ||
+ | |||
+ | #define halt(s) { Serial.println(F( s )); while(1); | ||
+ | |||
+ | / | ||
+ | |||
+ | // Setup a feed called ' | ||
+ | // Notice MQTT paths for AIO follow the form: < | ||
+ | Adafruit_MQTT_Publish numbers = Adafruit_MQTT_Publish(& | ||
+ | |||
+ | // Setup a feed called ' | ||
+ | Adafruit_MQTT_Subscribe onoffmsg = Adafruit_MQTT_Subscribe(& | ||
+ | |||
+ | |||
+ | |||
+ | / | ||
+ | |||
+ | // | ||
+ | --------------------------- Variables & others | ||
+ | -------------------------------- | ||
+ | rgb_lcd lcd; | ||
+ | byte heart[8] = { | ||
+ | 0b00000, | ||
+ | 0b01010, | ||
+ | 0b11111, | ||
+ | 0b11111, | ||
+ | 0b11111, | ||
+ | 0b01110, | ||
+ | 0b00100, | ||
+ | 0b00000 | ||
+ | }; | ||
+ | |||
+ | void setup() { | ||
+ | // | ||
+ | ------------------------- Initialization Serial | ||
+ | ------------------------------ | ||
+ | while (!Serial); | ||
+ | Serial.begin(9600); | ||
+ | |||
+ | // Serial.println(F(" | ||
+ | |||
+ | // | ||
+ | ---------------------- Initialization Wifi WINC1500 | ||
+ | -------------------------- | ||
+ | // Initialise the Client | ||
+ | // Serial.print(F(" | ||
+ | // check for the presence of the breakout | ||
+ | if (WiFi.status() == WL_NO_SHIELD) { | ||
+ | Serial.println(F(" | ||
+ | // don't continue: | ||
+ | while (true); | ||
+ | } | ||
+ | // Serial.println(F(" | ||
+ | |||
+ | // | ||
+ | ------------------------------ Physical part | ||
+ | ---------------------------------- | ||
+ | |||
+ | // Serial.println(F(" | ||
+ | // set up the LCD's number of columns and rows: | ||
+ | lcd.begin(16, | ||
+ | |||
+ | // Print a message to the LCD. | ||
+ | #if 1 | ||
+ | lcd.createChar(0, | ||
+ | #endif | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(" | ||
+ | lcd.write((unsigned char)0); | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(" | ||
+ | |||
+ | pinMode(RELAY2, | ||
+ | |||
+ | // | ||
+ | --------------------------- Variables & others | ||
+ | -------------------------------- | ||
+ | mqtt.subscribe(& | ||
+ | } | ||
+ | |||
+ | // uint32_t x = 0; | ||
+ | |||
+ | void loop() { | ||
+ | // | ||
+ | ------------------------------ MQTT Connection | ||
+ | -------------------------------- | ||
+ | // Ensure the connection to the MQTT server is alive (this will make the first | ||
+ | // connection and automatically reconnect when disconnected). | ||
+ | MQTT_connect(); | ||
+ | // lcd.setRGB(255, | ||
+ | // | ||
+ | ------------------------------ Subscription | ||
+ | ----------------------------------- | ||
+ | Adafruit_MQTT_Subscribe *subscription; | ||
+ | while ((subscription = mqtt.readSubscription(5000))) { | ||
+ | if (subscription == & | ||
+ | Serial.print(F(" | ||
+ | Serial.println((char *)onoffmsg.lastread); | ||
+ | |||
+ | if (0 == strcmp((char *)onoffmsg.lastread, | ||
+ | lcd.setRGB(255, | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(" | ||
+ | lcd.print(F(USERNAME)); | ||
+ | digitalWrite(RELAY2, | ||
+ | } | ||
+ | if (0 == strcmp((char *)onoffmsg.lastread, | ||
+ | lcd.setRGB(0, | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(" | ||
+ | lcd.print(F(USERNAME)); | ||
+ | digitalWrite(RELAY2, | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // | ||
+ | ------------------------------ Publication | ||
+ | ---------------------------------- | ||
+ | int32_t val = mesureSimple(analogInPin); | ||
+ | if (val < 0) { | ||
+ | lcd.setRGB(245, | ||
+ | } else { | ||
+ | lcd.setRGB(0, | ||
+ | } | ||
+ | |||
+ | |||
+ | // int32_t val = mesure(analogInPin) * 100; | ||
+ | |||
+ | Serial.print(F(" | ||
+ | Serial.print(val); | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(" | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(F(USERNAME)); | ||
+ | lcd.setCursor(9, | ||
+ | lcd.print(val); | ||
+ | |||
+ | // Si on met double au lieu de int32_t, plantage !!! | ||
+ | if (! numbers.publish(val)) { | ||
+ | Serial.println(F(" | ||
+ | } else { | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | // Function to connect and reconnect as necessary to the MQTT server. | ||
+ | // Should be called in the loop function and it will take care if connecting. | ||
+ | void MQTT_connect() { | ||
+ | int8_t ret; | ||
+ | |||
+ | // attempt to connect to Wifi network: | ||
+ | while (WiFi.status() != WL_CONNECTED) { | ||
+ | Serial.print(F(" | ||
+ | Serial.println(ssid); | ||
+ | // Connect to WPA/WPA2 network. Change this line if using open or WEP network: | ||
+ | status = WiFi.begin(ssid, | ||
+ | |||
+ | // wait 10 seconds for connection: | ||
+ | uint8_t timeout = 10; | ||
+ | while (timeout && (WiFi.status() != WL_CONNECTED)) { | ||
+ | timeout--; | ||
+ | delay(1000); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Stop if already connected. | ||
+ | if (mqtt.connected()) { | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | Serial.print(F(" | ||
+ | |||
+ | while ((ret = mqtt.connect()) != 0) { // connect will return 0 for connected | ||
+ | Serial.println(mqtt.connectErrorString(ret)); | ||
+ | Serial.println(ret); | ||
+ | Serial.println(F(" | ||
+ | mqtt.disconnect(); | ||
+ | delay(5000); | ||
+ | } | ||
+ | Serial.println(F(" | ||
+ | } | ||
+ | double mesure(int analogPin) { | ||
+ | |||
+ | const int ACSoffset = 2500; | ||
+ | double Amps = 0; | ||
+ | /*double Voltage = 0; | ||
+ | int RawValue= 0; | ||
+ | RawValue = analogRead(analogPin); | ||
+ | /*Voltage = (RawValue / 1024.0) * 5000; // Gets you mV | ||
+ | Amps = ((Voltage | ||
+ | - ACSoffset) / mVperAmp); | ||
+ | |||
+ | Amps = (((analogRead(analogPin) / 1024.0) * 5000 | ||
+ | - ACSoffset) / mVperAmp); | ||
+ | |||
+ | / | ||
+ | Serial.print(RawValue); | ||
+ | Serial.print(" | ||
+ | Serial.print(Voltage, | ||
+ | // the ' | ||
+ | Serial.print(" | ||
+ | Serial.println(Amps, | ||
+ | // the ' | ||
+ | delay(2500); | ||
+ | return (Amps); | ||
+ | } | ||
+ | int mesureSimple(int analogPin) { | ||
+ | |||
+ | int Valeur = analogRead(analogPin); | ||
+ | |||
+ | return (Valeur); | ||
+ | } | ||
+ | </ | ||
+ | ===== Node-RED ===== | ||
+ | |||
+ | < | ||
+ | [ | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | } | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | ], | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [], | ||
+ | [] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | }, | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | "# | ||
+ | ], | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | [], | ||
+ | [] | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | </ | ||
- | {{https:// |