Browse Source

Mise à jour DOC

Gestion esp32-c3-supermini ET esp32dev
master
scayac 3 months ago
parent
commit
5cc9f428ce
  1. 305
      README.md
  2. 30
      index.html
  3. 46
      lib/README
  4. 15
      platformio.ini
  5. 18
      quiz_exemple.txt
  6. 14
      src/main.cpp
  7. 11
      test/README

305
README.md

@ -1,24 +1,22 @@ @@ -1,24 +1,22 @@
# ESP32 Bluetooth HTML Monitor
# BleQuiz
Système de surveillance Bluetooth permettant de connecter plusieurs modules ESP32 et d'afficher l'état de leurs GPIOs en temps réel sur une page web.
Application HTML5 de quiz temps réel pour plusieurs modules ESP32 via Bluetooth Low Energy (BLE)
## 📋 Fonctionnalités
## 📋 Description
- ✅ Connexion de plusieurs modules ESP32 via Bluetooth (Web Bluetooth API)
- ✅ Affichage en temps réel de l'état des GPIOs
- ✅ Interface web moderne et responsive
- ✅ Notifications visuelles lors des changements d'état
- ✅ Gestion automatique de la reconnexion
BleQuiz permet de créer un système de quiz interactif en temps réel où plusieurs ESP32 peuvent être connectés simultanément via Bluetooth. Chaque module ESP32 surveille des GPIOs (boutons) et envoie instantanément les événements à une interface web HTML5.
**Cas d'usage** : Quiz en classe, jeux de rapidité, systèmes de vote, buzzers interactifs.
## 🔧 Configuration
### ESP32 (src/main.cpp)
Par défaut, le code surveille 4 GPIOs : **15, 16, 17, 18**
Par défaut, le code surveille 4 GPIOs : **2, 4, 5, 6** (compatibles ESP32 Wrover et ESP32-C3)
Pour modifier les GPIOs surveillés, éditez cette ligne :
```cpp
const int GPIO_PINS[] = {15, 16, 17, 18}; // Modifier selon vos besoins
const int GPIO_PINS[] = {2, 4, 5, 6}; // GPIOs à surveiller (A, B, C, D)
```
Pour changer le nom du module (important pour différencier plusieurs ESP32) :
@ -35,39 +33,69 @@ Le système envoie une notification à la page HTML **quand un GPIO passe à l' @@ -35,39 +33,69 @@ Le système envoie une notification à la page HTML **quand un GPIO passe à l'
## 🚀 Installation et utilisation
### 1. Compiler et téléverser sur ESP32
### 1. Prérequis
- **PlatformIO** installé (via VS Code ou CLI)
- **Python 3** (pour le serveur HTTPS)
- **Navigateur compatible** : Chrome, Edge ou Opera (Firefox et Safari ne supportent pas Web Bluetooth)
### 2. Configuration des ESP32
Le projet supporte deux environnements (voir `platformio.ini`) :
- **esp32dev** : ESP32 Wrover standard
- **esp32-c3-supermini** : Seeed XIAO ESP32-C3 (ou module générique ESP32-C3)
**Compiler et téléverser pour ESP32 Wrover** :
```bash
# Compiler le projet
pio run
pio run -e esp32dev
pio run -e esp32dev --target upload
```
# Téléverser sur l'ESP32
pio run --target upload
**Compiler et téléverser pour ESP32-C3** :
```bash
pio run -e esp32-c3-supermini
pio run -e esp32-c3-supermini --target upload
```
# Monitorer le port série (optionnel)
**Monitorer le port série** (optionnel) :
```bash
pio device monitor
```
### 2. Démarrer le serveur HTTPS
**Important** : Si vous utilisez plusieurs modules, modifiez le nom de chaque module dans `src/main.cpp` :
```cpp
String moduleName = "ESP32-Module-1"; // Changer pour Module-2, Module-3, etc.
```
### 3. Démarrer le serveur HTTPS
**Important** : Web Bluetooth nécessite HTTPS (ou localhost en HTTP)
**Option A - Serveur Python (recommandé)** :
**Option A - Script automatique (Linux)** :
```bash
./start.sh
```
Ce script :
- Démarre automatiquement le serveur Python HTTPS
- Lance Chrome avec les flags Web Bluetooth appropriés
- Ouvre directement l'application à `https://localhost:8443`
**Option B - Serveur Python manuel** :
```bash
python3 server.py
```
**Option B - Serveur Node.js** :
**Option C - Serveur Node.js** :
```bash
node server.js
```
Les deux serveurs :
Les serveurs Python et Node.js :
- Génèrent automatiquement un certificat SSL auto-signé
- Démarrent sur `https://localhost:8443`
- Servent la page HTML avec support Bluetooth
### 3. Ouvrir dans le navigateur
### 4. Ouvrir dans le navigateur
Ouvrez **Chrome**, **Edge** ou **Opera** et accédez à :
```
@ -77,13 +105,14 @@ https://localhost:8443 @@ -77,13 +105,14 @@ https://localhost:8443
**Note** :
- Vous verrez un avertissement de sécurité (certificat auto-signé) → Cliquez sur "Avancé" puis "Continuer vers localhost"
- Firefox et Safari ne supportent pas encore Web Bluetooth
- Sur Linux, utilisez le script `start.sh` pour lancer Chrome avec les bons paramètres
### 3. Connecter les modules
### 5. Utiliser l'application
1. Cliquez sur **"Connecter un module"**
2. Sélectionnez votre ESP32 dans la liste
3. Les GPIOs s'affichent en temps réel
4. Répétez pour connecter plusieurs modules
1. **Charger un fichier de questions** : Cliquez sur "Choisir un fichier" et sélectionnez votre fichier de quiz (voir `quiz_exemple.txt`)
2. **Connecter les modules** : Cliquez sur "Connecter un module" et sélectionnez votre ESP32
3. **Lancer le quiz** : Naviguez entre les questions, les modules répondent en appuyant sur leurs boutons
4. **Répétez** pour connecter plusieurs modules simultanément
## 🌐 Pourquoi HTTPS est nécessaire ?
@ -96,47 +125,88 @@ Les serveurs fournis (`server.py` et `server.js`) génèrent automatiquement un @@ -96,47 +125,88 @@ Les serveurs fournis (`server.py` et `server.js`) génèrent automatiquement un
## 🔌 Branchement des GPIOs
Pour tester, connectez vos GPIOs à la masse (GND) :
Pour tester, connectez vos boutons/switches entre les GPIOs et la masse (GND) :
```
ESP32 Bouton/Capteur
GPIO 15 ---- [Switch] ---- GND
GPIO 16 ---- [Switch] ---- GND
GPIO 17 ---- [Switch] ---- GND
GPIO 18 ---- [Switch] ---- GND
ESP32 Bouton/Switch
GPIO 2 ---- [Bouton A] ---- GND
GPIO 4 ---- [Bouton B] ---- GND
GPIO 5 ---- [Bouton C] ---- GND
GPIO 6 ---- [Bouton D] ---- GND
```
## 📊 Format des données
**Configuration** : Les GPIOs utilisent des résistances de pull-up internes, donc :
- État normal (bouton non pressé) : HIGH (3.3V)
- État actif (bouton pressé) : LOW (0V / GND)
## 📁 Structure du projet
```
BleQuiz/
├── index.html # Interface web du quiz
├── platformio.ini # Configuration PlatformIO (esp32dev, esp32-c3)
├── server.py # Serveur HTTPS Python
├── server.js # Serveur HTTPS Node.js
├── start.sh # Script de lancement automatique (Linux)
├── quiz_exemple.txt # Exemple de fichier de questions
├── src/
│ └── main.cpp # Code ESP32 (BLE + GPIO)
└── include/
└── README
```
Les données sont envoyées en JSON :
## 📊 Format des données BLE
Les données sont envoyées en JSON via Bluetooth Low Energy :
```json
{
"module": "ESP32-Module-1",
"gpios": [
{"pin": 15, "state": "0"},
{"pin": 16, "state": "1"},
{"pin": 17, "state": "0"},
{"pin": 18, "state": "0"}
{"pin": 2, "state": "0"},
{"pin": 4, "state": "1"},
{"pin": 5, "state": "0"},
{"pin": 6, "state": "0"}
]
}
```
- `state: "0"` = GPIO HIGH (état normal)
- `state: "1"` = GPIO LOW (état actif)
- `state: "0"` = GPIO HIGH (bouton non pressé)
- `state: "1"` = GPIO LOW (bouton pressé)
## 🎨 Interface
## 🎨 Fonctionnalités de l'interface
L'interface affiche :
- Le nom de chaque module connecté
- L'état de chaque GPIO (HAUT/BAS) avec code couleur
- L'horodatage de la dernière mise à jour
- Des animations visuelles lors des changements d'état
L'application web offre :
- **Chargement de quiz** : Support de fichiers texte avec questions et réponses
- **Connexion multi-modules** : Connectez jusqu'à 10 ESP32 simultanément
- **Affichage en temps réel** : État des boutons de chaque module avec code couleur
- **Système de scoring** : Points automatiques pour les bonnes réponses
- **Mode de jeu** : Détection du premier à répondre avec verrouillage
- **Animations visuelles** : Feedback instantané lors des appuis
- **Historique** : Suivi des réponses et du temps de réaction
## ⚙ Personnalisation
### Changer le nom du module ESP32
Dans `src/main.cpp`, modifiez :
```cpp
String moduleName = "ESP32-Module-1"; // Changer pour chaque ESP32 (Module-2, Module-3, etc.)
```
### Modifier les GPIOs surveillés
Dans `src/main.cpp`, adaptez selon votre matériel :
```cpp
const int GPIO_PINS[] = {2, 4, 5, 6}; // GPIOs à surveiller
```
**GPIOs recommandés** :
- **ESP32 Wrover** : 2, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23
- **ESP32-C3** : 2, 3, 4, 5, 6, 7, 8, 9, 10
### Changer les UUID Bluetooth
Dans `main.cpp` et `index.html`, modifiez :
Dans `src/main.cpp` et `index.html`, modifiez (doit être identique des deux côtés) :
```cpp
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
@ -144,37 +214,134 @@ Dans `main.cpp` et `index.html`, modifiez : @@ -144,37 +214,134 @@ Dans `main.cpp` et `index.html`, modifiez :
### Modifier le délai de lecture
Dans `main.cpp`, ligne finale :
Dans `src/main.cpp`, dernière ligne de `loop()` :
```cpp
delay(100); // Modifier selon vos besoins (en millisecondes)
```
## 🐛 Dépannage
### Format du fichier de quiz
**L'ESP32 n'apparaît pas dans la liste Bluetooth**
- Vérifiez que le module est alimenté
- Consultez le moniteur série pour voir les messages de démarrage
- Assurez-vous que le Bluetooth est activé sur votre PC
Créez un fichier texte avec ce format (voir `quiz_exemple.txt`) :
```
* Quelle est la formule de l'ion fer(III) ?
- Fe^(2+)
+ Fe^(3+)
- Fe_(2)^(3-)
- Fe_(3)^(2+)
* Question suivante ?
- Mauvaise réponse
- Autre mauvaise réponse
+ Bonne réponse
- Encore une mauvaise réponse
```
**Format** :
- `*` = Question
- `-` = Mauvaise réponse
- `+` = Bonne réponse
**Pas de données reçues**
- Vérifiez les UUID dans le code ESP32 et HTML
- Consultez la console JavaScript (F12) pour les erreurs
- Testez en connectant un GPIO à GND
**Syntaxe pour indices et exposants** :
- `^(texte)` = exposant → `Fe^(3+)` s'affiche comme Fe³⁺
- `_(texte)` = indice → `H_(2)O` s'affiche comme H₂O
- Combinaison possible : `Fe_(2)O_(3)` → Fe₂O₃
**Déconnexions fréquentes**
- Vérifiez la distance entre l'ESP32 et le PC
- Assurez-vous que l'alimentation de l'ESP32 est stable
## 🐛 Dépannage
### L'ESP32 n'apparaît pas dans la liste Bluetooth
- Vérifiez que le module est alimenté correctement (USB ou externe 5V)
- Consultez le moniteur série : `pio device monitor` pour voir les messages de démarrage
- Assurez-vous que le Bluetooth est activé sur votre ordinateur
- Sur Linux, vérifiez que BlueZ est à jour : `bluetoothctl --version`
- Redémarrez l'ESP32 (bouton RESET ou débrancher/rebrancher)
### Pas de données reçues dans l'interface web
- Vérifiez que les UUID correspondent dans `src/main.cpp` et `index.html`
- Consultez la console JavaScript (F12) pour voir les erreurs
- Testez en connectant un GPIO à GND avec un simple fil
- Vérifiez que le module est bien connecté (voyant dans l'interface)
### Déconnexions fréquentes
- Réduisez la distance entre l'ESP32 et l'ordinateur (< 5 mètres)
- Assurez-vous que l'alimentation de l'ESP32 est stable (câble USB de qualité)
- Évitez les interférences (WiFi 2.4GHz, micro-ondes)
- Augmentez le délai dans la boucle principale si trop de données sont envoyées
### Erreur "HTTPS required" ou "Bluetooth API not available"
- Vérifiez que vous accédez bien à `https://localhost:8443` (pas `http://`)
- Utilisez Chrome, Edge ou Opera (pas Firefox ni Safari)
- Sur Linux, utilisez le script `./start.sh` pour les bons paramètres Chrome
### Erreur de compilation PlatformIO
- Vérifiez votre environnement dans `platformio.ini`
- Mettez à jour PlatformIO : `pio upgrade`
- Nettoyez le projet : `pio run --target clean`
- Réinstallez les dépendances : `pio pkg update`
## 📝 Caractéristiques techniques
- **Protocole** : Bluetooth Low Energy (BLE 4.0+)
- **Compatibilité ESP32** : ESP32 Wrover, ESP32-C3 (Seeed XIAO)
- **GPIOs configurables** : 4 par défaut (extensible)
- **Connexions simultanées** : Jusqu'à 10 modules ESP32
- **Fréquence de mise à jour** : 100ms par défaut
- **Portée Bluetooth** : ~10 mètres en intérieur
- **Navigateurs supportés** : Chrome 56+, Edge 79+, Opera 43+
- **Serveurs fournis** : Python 3 (SSL auto-signé) et Node.js
## 🎯 Notes d'utilisation
- **Modules multiples** : Chaque module doit avoir un nom unique configuré dans `src/main.cpp`
- **Sécurité BLE** : Le code utilise un mode sans bonding (pas de mémorisation d'appairage)
- **Web Bluetooth** : La page HTML doit obligatoirement être servie en HTTPS ou via localhost
- **Détection de changement** : Les événements GPIO sont envoyés instantanément lors des transitions HIGH→LOW
- **Mode Pull-up** : Les boutons doivent connecter le GPIO à GND (pas à VCC)
## 🚀 Démarrage rapide (Linux)
## 📝 Notes
```bash
# 1. Compiler et téléverser le code ESP32
pio run -e esp32dev --target upload
- Plusieurs modules peuvent être connectés simultanément
- Chaque module doit avoir un nom unique
- La page HTML doit être servie en HTTPS ou localhost pour Web Bluetooth
- Les changements d'état sont détectés et envoyés automatiquement
# 2. Lancer l'application (serveur + navigateur)
./start.sh
# 3. Dans l'interface web :
# - Charger un fichier de quiz
# - Connecter les modules ESP32
# - Commencer le quiz !
```
## 🔐 Sécurité
Pour un usage en production, considérez :
- Ajouter une authentification Bluetooth
- Chiffrer les données transmises
- Implémenter une validation côté serveur
Le projet est conçu pour un usage éducatif et de démonstration. Pour un usage en production :
- **Authentification BLE** : Implémenter un système de pairing sécurisé
- **Chiffrement des données** : Activer le chiffrement des caractéristiques BLE
- **Certificat SSL valide** : Remplacer le certificat auto-signé par un certificat CA
- **Validation côté serveur** : Ajouter une validation des réponses côté serveur
- **Limitation de connexions** : Implémenter une limite et un contrôle d'accès
- **Logs d'audit** : Enregistrer les actions pour le suivi et la sécurité
## 📚 Ressources
- [Web Bluetooth API Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API)
- [ESP32 BLE Arduino Documentation](https://github.com/espressif/arduino-esp32/tree/master/libraries/BLE)
- [PlatformIO Documentation](https://docs.platformio.org/)
- [ESP32 GPIO Reference](https://randomnerdtutorials.com/esp32-pinout-reference-gpios/)
## 📄 Licence
Ce projet est open source. Libre d'utilisation, modification et distribution.
## 👥 Contribution
Les contributions sont les bienvenues ! N'hésitez pas à :
- Signaler des bugs
- Proposer de nouvelles fonctionnalités
- Améliorer la documentation
- Soumettre des pull requests
---
**Créé avec ❤ pour l'éducation et l'apprentissage interactif**

30
index.html

@ -461,6 +461,17 @@ @@ -461,6 +461,17 @@
.notification.hiding {
animation: slideOut 0.3s ease-in forwards;
}
/* Styles pour indices et exposants */
sub {
font-size: 0.75em;
vertical-align: sub;
}
sup {
font-size: 0.75em;
vertical-align: super;
}
</style>
</head>
<body>
@ -540,6 +551,15 @@ @@ -540,6 +551,15 @@
const correctScoreInput = document.getElementById('correctScore');
const incorrectScoreInput = document.getElementById('incorrectScore');
// Fonction pour convertir les indices et exposants
function formatText(text) {
// Remplacer _(contenu) par <sub>contenu</sub>
text = text.replace(/_\(([^)]+)\)/g, '<sub>$1</sub>');
// Remplacer ^(contenu) par <sup>contenu</sup>
text = text.replace(/\^\(([^)]+)\)/g, '<sup>$1</sup>');
return text;
}
// Fonction pour afficher une notification
function showNotification(message, type = 'info', duration = 5000) {
const notification = document.createElement('div');
@ -698,16 +718,16 @@ @@ -698,16 +718,16 @@
console.log('📊 GPIOs pour', moduleName, ':', data.gpios);
// Détecter la réponse (A=GPIO15, B=GPIO4, C=GPIO5, D=GPIO18)
// Détecter la réponse (A=GPIO2, B=GPIO4, C=GPIO5, D=GPIO6)
// Réponse détectée si GPIO est à l'état BAS (1)
let answer = null;
data.gpios.forEach(gpio => {
console.log(` GPIO ${gpio.pin} = ${gpio.state} (type: ${typeof gpio.state})`);
if (String(gpio.state) === '1') {
if (gpio.pin === 15) answer = 'A';
if (gpio.pin === 2) answer = 'A';
else if (gpio.pin === 4) answer = 'B';
else if (gpio.pin === 5) answer = 'C';
else if (gpio.pin === 18) answer = 'D';
else if (gpio.pin === 6) answer = 'D';
}
});
@ -946,7 +966,7 @@ @@ -946,7 +966,7 @@
// Afficher la question
document.querySelector('.question-number').textContent =
`Question ${currentQuestionIndex + 1}/${questions.length}`;
document.querySelector('.question-text').textContent = question.question;
document.querySelector('.question-text').innerHTML = formatText(question.question);
// Afficher les réponses
const answersContainer = document.getElementById('answersContainer');
@ -960,7 +980,7 @@ @@ -960,7 +980,7 @@
answerDiv.innerHTML = `
<div class="answer-content">
<div class="answer-letter">${letters[index]}</div>
<div>${answer}</div>
<div>${formatText(answer)}</div>
</div>
`;

46
lib/README

@ -1,46 +0,0 @@ @@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into the executable file.
The source code of each library should be placed in a separate directory
("lib/your_library_name/[Code]").
For example, see the structure of the following example libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
Example contents of `src/main.c` using Foo and Bar:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
The PlatformIO Library Dependency Finder will find automatically dependent
libraries by scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

15
platformio.ini

@ -4,16 +4,9 @@ board = esp32dev @@ -4,16 +4,9 @@ board = esp32dev
framework = arduino
monitor_speed = 115200
[env:esp32-s3-supermini]
[env:esp32-c3-supermini]
platform = espressif32
board = esp32-s3-devkitc-1
board = seeed_xiao_esp32c3
board_build.mcu = esp32c3
framework = arduino
monitor_speed = 115200
board_build.arduino.memory_type = qio_qspi
board_build.flash_mode = qio
board_build.psram_type = qio
board_upload.flash_size = 4MB
board_upload.maximum_size = 4194304
board_build.partitions = default.csv
board_build.extra_flags =
-DBOARD_HAS_PSRAM
monitor_speed = 115200

18
quiz_exemple.txt

@ -23,7 +23,19 @@ @@ -23,7 +23,19 @@
- 6
* Quel est le symbole chimique de l'eau ?
- CO2
- O2
+ H2O
- CO_(2)
- O_(2)
+ H_(2)O
- NaCl
* Quelle est la formule de l'ion fer(III) ?
- Fe^(2+)
+ Fe^(3+)
- Fe_(2)^(3-)
- Fe_(3)^(2+)
* Comment écrit-on l'ion sulfate ?
+ SO_(4)^(2-)
- SO_(3)^(2-)
- S_(2)O_(4)^(2-)
- SO_(2)^(2-)

14
src/main.cpp

@ -9,9 +9,8 @@ @@ -9,9 +9,8 @@
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Configuration des GPIO à surveiller
// Note: GPIO 16 et 17 peuvent poser problème (UART2/PSRAM)
// Utilisation de GPIO plus fiables: 15, 4, 5, 18
const int GPIO_PINS[] = {15, 4, 5, 18}; // GPIOs à surveiller (A, B, C, D)
// GPIO compatibles ESP32 Wrover ET ESP32-C3 : 2, 4, 5, 6
const int GPIO_PINS[] = {2, 4, 5, 6}; // GPIOs à surveiller (A, B, C, D)
const int NUM_PINS = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]);
// Variables globales
@ -22,7 +21,7 @@ bool oldDeviceConnected = false; @@ -22,7 +21,7 @@ bool oldDeviceConnected = false;
uint8_t gpioStates = 0; // Stocke l'état des GPIOs (bit à bit)
// Nom du module (peut être modifié pour chaque ESP32)
String moduleName = "ESP32S3-Module-1";
String moduleName = "ESP32-Module-1";
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
@ -48,6 +47,13 @@ void setup() { @@ -48,6 +47,13 @@ void setup() {
// Initialisation BLE
BLEDevice::init(moduleName.c_str());
// --- Sécurité BLE : mode sans bonding (pas de mémorisation d'appairage) ---
// Cela permet des reconnexions après reboot sans problème de clés obsolètes
BLESecurity *pSecurity = new BLESecurity();
pSecurity->setAuthenticationMode(ESP_LE_AUTH_NO_BOND); // Pas de bonding, connexion temporaire
pSecurity->setCapability(ESP_IO_CAP_NONE); // Pas de clavier/écran
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
// Créer le serveur BLE
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());

11
test/README

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
Loading…
Cancel
Save