Browse Source

Initial commit

master
scayac 3 months ago
commit
9609989102
  1. 3
      .gitignore
  2. 180
      README.md
  3. 22
      cert.pem
  4. 37
      include/README
  5. 471
      index.html
  6. 28
      key.pem
  7. 46
      lib/README
  8. 15
      platformio.ini
  9. 101
      server.js
  10. 76
      server.py
  11. 134
      src/main.cpp
  12. 79
      start.sh
  13. 11
      test/README

3
.gitignore vendored

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
.pio
.vscode
.git

180
README.md

@ -0,0 +1,180 @@ @@ -0,0 +1,180 @@
# ESP32 Bluetooth HTML Monitor
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.
## 📋 Fonctionnalités
- ✅ 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
## 🔧 Configuration
### ESP32 (src/main.cpp)
Par défaut, le code surveille 4 GPIOs : **15, 16, 17, 18**
Pour modifier les GPIOs surveillés, éditez cette ligne :
```cpp
const int GPIO_PINS[] = {15, 16, 17, 18}; // Modifier selon vos besoins
```
Pour changer le nom du module (important pour différencier plusieurs ESP32) :
```cpp
String moduleName = "ESP32-Module-1"; // Changer pour chaque ESP32
```
### GPIOs avec Pull-up
Les GPIOs sont configurés en **INPUT_PULLUP**, ce qui signifie :
- **État normal (non connecté)** : HIGH (3.3V)
- **État actif (connecté à GND)** : LOW (0V)
Le système envoie une notification à la page HTML **quand un GPIO passe à l'état BAS**.
## 🚀 Installation et utilisation
### 1. Compiler et téléverser sur ESP32
```bash
# Compiler le projet
pio run
# Téléverser sur l'ESP32
pio run --target upload
# Monitorer le port série (optionnel)
pio device monitor
```
### 2. Démarrer le serveur HTTPS
**Important** : Web Bluetooth nécessite HTTPS (ou localhost en HTTP)
**Option A - Serveur Python (recommandé)** :
```bash
python3 server.py
```
**Option B - Serveur Node.js** :
```bash
node server.js
```
Les deux serveurs :
- 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
Ouvrez **Chrome**, **Edge** ou **Opera** et accédez à :
```
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
### 3. Connecter les modules
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
## 🌐 Pourquoi HTTPS est nécessaire ?
Web Bluetooth API nécessite un **contexte sécurisé** pour des raisons de sécurité :
- ✅ `https://` (avec certificat SSL)
- ✅ `http://localhost` ou `http://127.0.0.1`
- ❌ `file://` (ouverture directe du fichier HTML)
Les serveurs fournis (`server.py` et `server.js`) génèrent automatiquement un certificat SSL auto-signé.
## 🔌 Branchement des GPIOs
Pour tester, connectez vos GPIOs à la masse (GND) :
```
ESP32 Bouton/Capteur
GPIO 15 ---- [Switch] ---- GND
GPIO 16 ---- [Switch] ---- GND
GPIO 17 ---- [Switch] ---- GND
GPIO 18 ---- [Switch] ---- GND
```
## 📊 Format des données
Les données sont envoyées en JSON :
```json
{
"module": "ESP32-Module-1",
"gpios": [
{"pin": 15, "state": "0"},
{"pin": 16, "state": "1"},
{"pin": 17, "state": "0"},
{"pin": 18, "state": "0"}
]
}
```
- `state: "0"` = GPIO HIGH (état normal)
- `state: "1"` = GPIO LOW (état actif)
## 🎨 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
## ⚙ Personnalisation
### Changer les UUID Bluetooth
Dans `main.cpp` et `index.html`, modifiez :
```cpp
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
```
### Modifier le délai de lecture
Dans `main.cpp`, ligne finale :
```cpp
delay(100); // Modifier selon vos besoins (en millisecondes)
```
## 🐛 Dépannage
**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
**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
**Déconnexions fréquentes**
- Vérifiez la distance entre l'ESP32 et le PC
- Assurez-vous que l'alimentation de l'ESP32 est stable
## 📝 Notes
- 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
## 🔐 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

22
cert.pem

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDjzCCAnegAwIBAgIUf44ZDI8lm8uItCwfYpCF/loe3SswDQYJKoZIhvcNAQEL
BQAwVzELMAkGA1UEBhMCRlIxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0y
NjAxMDUwODEwMDFaFw0yNzAxMDUwODEwMDFaMFcxCzAJBgNVBAYTAkZSMQ4wDAYD
VQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UECgwMT3JnYW5pemF0aW9u
MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7xWCAyFV83S56WwBO2tDZSCc31Rdbp2U3hMmk7rkNFZxYKQx02eIIcM7S
6W0cDiSf8slOelBPHfCk9Z4IhBN0P+VltrBDneGyYPvgF2ndtaYLak4qof2zTUnw
i2vyiZY3Srny0BUB8Y6LrXXAo6NBZCi5iOJ4plpw9PfREzj/kubcqEr+zA8t66/Z
ZmN9fSEp1PB8kEjwsSNuI7Gi72IYLQoAoLBZo08qwI1T+nUICq4/EEXR+6BR1zXd
TeghbD+s+snH2JutwUcLAo+10NylD9Zl/81u10Q4ySgjcv1KBweqLpzLv6jTIMMK
l7lEf1Ml7G3u+99Bilo8PffGFCCxAgMBAAGjUzBRMB0GA1UdDgQWBBSts6v8ASbq
YyyXyiaaY8HvByuPMTAfBgNVHSMEGDAWgBSts6v8ASbqYyyXyiaaY8HvByuPMTAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBr8GKZF7wmymVRsu3m
QEnGcsCCqNhI4xhEIfbEZ8IcBRcCBq7/0bV71yh0UGDi5Q7cXF0F6z7dl8+QlHWS
UuLSN4Kk4z/3E8qtN4vxTIzf6vpXqdizmOp4gHO+LZfBBOuu5oZuN/vE8GV29hbU
obzPwdzU95zPcGDKGOIUp+Zo2vWbqPUHmZkitPC+8WSQHxsifJez9BkP5jy9Cy4T
rGepv2wY7WNB9eEfdb/QKKqLRRH8p0S3w3ryM37e3INRTE+KuLGyeyX0wDLTxcml
K0Am+KntsaiATbbJjZykKXPkOdF02w2WK2d5XAx/EWXk3WuzEGSNuBo+/4ms6+LX
hkzu
-----END CERTIFICATE-----

37
include/README

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the convention is to give header files names that end with `.h'.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

471
index.html

@ -0,0 +1,471 @@ @@ -0,0 +1,471 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Bluetooth Monitor</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: white;
text-align: center;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.controls {
background: white;
padding: 20px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
margin-bottom: 30px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
#connectBtn {
background: #4CAF50;
color: white;
}
#connectBtn:hover {
background: #45a049;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
}
#connectBtn:disabled {
background: #cccccc;
cursor: not-allowed;
transform: none;
}
.status {
flex: 1;
text-align: right;
font-weight: 600;
color: #666;
}
.modules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.module-card {
background: white;
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
transition: transform 0.3s ease;
}
.module-card:hover {
transform: translateY(-5px);
}
.module-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 3px solid #667eea;
}
.module-name {
font-size: 1.5em;
font-weight: bold;
color: #667eea;
}
.disconnect-btn {
background: #f44336;
color: white;
padding: 8px 16px;
font-size: 14px;
}
.disconnect-btn:hover {
background: #da190b;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(244, 67, 54, 0.4);
}
.gpio-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.gpio-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background: #f5f5f5;
border-radius: 10px;
transition: all 0.3s ease;
}
.gpio-item.active {
background: #ffebee;
border-left: 5px solid #f44336;
}
.gpio-label {
font-weight: 600;
color: #333;
font-size: 1.1em;
}
.gpio-status {
padding: 8px 20px;
border-radius: 20px;
font-weight: bold;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 1px;
}
.gpio-status.low {
background: #f44336;
color: white;
animation: pulse 1.5s infinite;
}
.gpio-status.high {
background: #4CAF50;
color: white;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: white;
font-size: 1.2em;
}
.empty-state-icon {
font-size: 4em;
margin-bottom: 20px;
opacity: 0.7;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
background: white;
padding: 15px 25px;
border-radius: 10px;
box-shadow: 0 5px 20px rgba(0,0,0,0.3);
z-index: 1000;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.last-update {
color: #999;
font-size: 0.85em;
margin-top: 10px;
text-align: right;
}
</style>
</head>
<body>
<div class="container">
<h1>🔵 ESP32 Bluetooth Monitor</h1>
<div class="controls">
<button id="connectBtn">Connecter un module</button>
<div class="status" id="status">Aucun module connecté</div>
</div>
<div class="modules-grid" id="modulesContainer">
<div class="empty-state">
<div class="empty-state-icon">📡</div>
<div>Cliquez sur "Connecter un module" pour ajouter un ESP32</div>
</div>
</div>
</div>
<script>
// Configuration BLE
const SERVICE_UUID = '4fafc201-1fb5-459e-8fcc-c5c9c331914b';
const CHARACTERISTIC_UUID = 'beb5483e-36e1-4688-b7f5-ea07361b26a8';
// Stockage des modules connectés
let connectedModules = new Map();
// Éléments DOM
const connectBtn = document.getElementById('connectBtn');
const statusDiv = document.getElementById('status');
const modulesContainer = document.getElementById('modulesContainer');
// Vérifier si Web Bluetooth est disponible
if (!navigator.bluetooth) {
alert('Web Bluetooth n\'est pas supporté par votre navigateur. Utilisez Chrome, Edge ou Opera.');
connectBtn.disabled = true;
}
// Fonction pour afficher une notification
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
// Fonction pour mettre à jour le statut
function updateStatus() {
const count = connectedModules.size;
statusDiv.textContent = count === 0
? 'Aucun module connecté'
: `${count} module${count > 1 ? 's' : ''} connecté${count > 1 ? 's' : ''}`;
}
// Fonction pour créer la carte d'un module
function createModuleCard(moduleName, device) {
const card = document.createElement('div');
card.className = 'module-card';
card.id = `module-${moduleName}`;
card.innerHTML = `
<div class="module-header">
<div class="module-name">${moduleName}</div>
<button class="disconnect-btn" onclick="disconnectModule('${moduleName}')">
Déconnecter
</button>
</div>
<div class="gpio-list" id="gpio-list-${moduleName}">
<div style="text-align: center; color: #999;">En attente de données...</div>
</div>
<div class="last-update" id="last-update-${moduleName}">
Dernière mise à jour: --
</div>
`;
return card;
}
// Fonction pour mettre à jour l'affichage des GPIO
function updateGPIODisplay(moduleName, gpioData) {
console.log(`[updateGPIODisplay] Module: ${moduleName}`, gpioData);
const gpioList = document.getElementById(`gpio-list-${moduleName}`);
const lastUpdate = document.getElementById(`last-update-${moduleName}`);
if (!gpioList) {
console.error(`[updateGPIODisplay] Élément gpio-list non trouvé pour ${moduleName}`);
return;
}
gpioList.innerHTML = '';
gpioData.forEach(gpio => {
// Convertir en string pour comparaison cohérente
const stateStr = String(gpio.state);
const isLow = (stateStr === '1' || stateStr === 1);
console.log(` GPIO ${gpio.pin}: state=${gpio.state} (${typeof gpio.state}), isLow=${isLow}`);
const gpioItem = document.createElement('div');
gpioItem.className = `gpio-item ${isLow ? 'active' : ''}`;
gpioItem.innerHTML = `
<div class="gpio-label">GPIO ${gpio.pin}</div>
<div class="gpio-status ${isLow ? 'low' : 'high'}">
${isLow ? 'BAS' : 'HAUT'}
</div>
`;
gpioList.appendChild(gpioItem);
});
// Mettre à jour l'horodatage
const now = new Date();
lastUpdate.textContent = `Dernière mise à jour: ${now.toLocaleTimeString()}`;
}
// Fonction pour connecter un module
async function connectModule() {
try {
console.log('Recherche d\'un appareil Bluetooth...');
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [SERVICE_UUID] }]
});
console.log('Appareil trouvé:', device.name);
showNotification(`Connexion à ${device.name}...`);
const server = await device.gatt.connect();
console.log('Connecté au serveur GATT');
const service = await server.getPrimaryService(SERVICE_UUID);
const characteristic = await service.getCharacteristic(CHARACTERISTIC_UUID);
// Lire l'état initial immédiatement
const initialValue = await characteristic.readValue();
const initialData = new TextDecoder().decode(initialValue);
console.log('État initial:', initialData);
// Traiter l'état initial
try {
const data = JSON.parse(initialData);
const moduleName = data.module;
// Créer la carte du module
const emptyState = modulesContainer.querySelector('.empty-state');
if (emptyState) emptyState.remove();
const card = createModuleCard(moduleName, device);
modulesContainer.appendChild(card);
// Stocker les informations
connectedModules.set(moduleName, {
device: device,
characteristic: characteristic,
gpios: data.gpios
});
// Afficher l'état initial
updateGPIODisplay(moduleName, data.gpios);
updateStatus();
showNotification(`${moduleName} connecté avec succès!`);
} catch (e) {
console.error('Erreur parsing état initial:', e);
}
// Activer les notifications pour les changements futurs
await characteristic.startNotifications();
console.log('Notifications activées');
// Écouter les notifications
characteristic.addEventListener('characteristicvaluechanged', (event) => {
const value = new TextDecoder().decode(event.target.value);
console.log('🔔 [Notification reçue]:', value);
try {
const data = JSON.parse(value);
const moduleName = data.module;
console.log(`📦 Données parsées:`, data);
// Mettre à jour les informations du module
if (connectedModules.has(moduleName)) {
console.log(`✅ Module ${moduleName} trouvé, mise à jour...`);
connectedModules.get(moduleName).gpios = data.gpios;
// Mettre à jour l'affichage
updateGPIODisplay(moduleName, data.gpios);
} else {
console.warn(`⚠ Module ${moduleName} non trouvé dans connectedModules`);
}
} catch (e) {
console.error('❌ Erreur lors du parsing JSON:', e);
}
});
// Gérer la déconnexion
device.addEventListener('gattserverdisconnected', () => {
console.log(`${device.name} déconnecté`);
showNotification(`${device.name} déconnecté`);
// Trouver et supprimer le module
for (let [name, module] of connectedModules.entries()) {
if (module.device === device) {
connectedModules.delete(name);
const card = document.getElementById(`module-${name}`);
if (card) card.remove();
break;
}
}
updateStatus();
// Afficher l'état vide si aucun module
if (connectedModules.size === 0) {
modulesContainer.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📡</div>
<div>Cliquez sur "Connecter un module" pour ajouter un ESP32</div>
</div>
`;
}
});
} catch (error) {
console.error('Erreur de connexion:', error);
showNotification(`Erreur: ${error.message}`);
}
}
// Fonction pour déconnecter un module
window.disconnectModule = function(moduleName) {
const module = connectedModules.get(moduleName);
if (module && module.device.gatt.connected) {
module.device.gatt.disconnect();
}
};
// Événement du bouton de connexion
connectBtn.addEventListener('click', connectModule);
</script>
</body>
</html>

28
key.pem

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7xWCAyFV83S56
WwBO2tDZSCc31Rdbp2U3hMmk7rkNFZxYKQx02eIIcM7S6W0cDiSf8slOelBPHfCk
9Z4IhBN0P+VltrBDneGyYPvgF2ndtaYLak4qof2zTUnwi2vyiZY3Srny0BUB8Y6L
rXXAo6NBZCi5iOJ4plpw9PfREzj/kubcqEr+zA8t66/ZZmN9fSEp1PB8kEjwsSNu
I7Gi72IYLQoAoLBZo08qwI1T+nUICq4/EEXR+6BR1zXdTeghbD+s+snH2JutwUcL
Ao+10NylD9Zl/81u10Q4ySgjcv1KBweqLpzLv6jTIMMKl7lEf1Ml7G3u+99Bilo8
PffGFCCxAgMBAAECggEAKYfSHKHSsKueFk9u3cINi+Vm0TVHNY0seK9cxydK1C9J
tYEcwXKYiRvkTud0q84gASWaq0on3ZvmaCHwOnt17xIb25eg3E+9Be5n/0xGbNR6
vY8da1YJbkFDv5mivlH6oM+dGay23Mdv6wpqXzmCSBoOXNz/FEbc/Ztc/YgGJRaY
MmIINmcZtFJ4Yhtdel7U93Ec0kIXijlBxjj4LkyBhxh3elJOeLbZQeo34QbdvpSw
7S3Oxrb0XznEm8EsVnomWfVEkWhtSu3ylSaxw1cpk7X8VbtMvLRz4wjGNArspQWg
dCi5xiYd+vR/a3TvI+CpqOem9IJvWntRzDI5/QPrzQKBgQDvDiMI4NLi19xVQT23
mKYIK+57DtiQqCnNtLhgmcM4dLhG+RCZgJTYgU5Ra8dwKIaNTpDewBrEb16mEC5G
L5r3/3W8nahmbS74hYyU+34rA6enzJ9IDcwWXfE3oHqWe8SVRbcG8pCfpS1y8Vr3
HoTm0fG1oiMufbtmSAyrY0J3PQKBgQDJFKTMjt2QE+qmmfJzaPX2iOvl5neXkdRi
nx9FKbWje+wRsxlFHWPRxkhXaN5ICMTTNiF0uKs1M6bHakv0nyj3G0sxlB5rfdeK
uKw43galsaUll7XJ5isqeRd4eSqOLodwlyRfXLPQJYWqwwzOo1JOznqmSHxzIS3I
br3VCs/GhQKBgQCsrsyWBDmyDXYmy6r2bbk52+o4UGAWFrOj2bD6bvg7Vu0qr43T
JLPgtX9Kh42Ysl2Hw8IYdjKfxUdctAGJR6gtDwcQid50ptgwQ+BqWkUoc0pvhvtk
RDsxyQiPQ0hqXduCbMqqpRvdhqcPCdPJAO3GAtAUgZviYIa3esJUJ66CjQKBgChe
SZt7jwWOygv6Wg9LYh3FbT5xX08BitblxRxYfEu+5CzFfOxAMzr5CKrrtbxWblVM
x7isHksG/JOKRodssIezZgwlBVplIDGMU57zC/iVymbapzdKSx4yw0B/asiylKRI
45d1f+/oqIYYtGiDp1GE6GCbqsheP9e+S8QKcJ4VAoGAGAL2KWgm6rHH/H4uR2Rj
A9TuoA2U1o9o0633FJaNkSsGE9qtkkGSUOH0d7i2bragJ9D7SvJPqiMrxMDUo579
tpDT3fa4ZpI3nBaVAiB35kJakC4big4pnrXNcUpE+Cmo4FKC/jQo1f4Dlly5CXXT
nzs1pRQmgMMtJFlb7O3QyZU=
-----END PRIVATE KEY-----

46
lib/README

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
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

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

101
server.js

@ -0,0 +1,101 @@ @@ -0,0 +1,101 @@
const https = require('https');
const fs = require('fs');
const path = require('path');
const { exec } = require('child_process');
const PORT = 8443;
// Générer un certificat auto-signé si nécessaire
function generateCertificate() {
return new Promise((resolve, reject) => {
const certPath = path.join(__dirname, 'cert.pem');
const keyPath = path.join(__dirname, 'key.pem');
// Vérifier si les certificats existent déjà
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
console.log('✓ Certificats existants trouvés');
resolve({ cert: certPath, key: keyPath });
return;
}
console.log('Génération d\'un certificat auto-signé...');
const cmd = `openssl req -newkey rsa:2048 -new -nodes -x509 -days 365 -keyout key.pem -out cert.pem -subj "/C=FR/ST=State/L=City/O=Organization/CN=localhost"`;
exec(cmd, (error) => {
if (error) {
console.error('Erreur lors de la génération du certificat:', error);
console.log('\n⚠ Installez OpenSSL ou créez les certificats manuellement');
reject(error);
return;
}
console.log('✓ Certificat généré avec succès');
resolve({ cert: certPath, key: keyPath });
});
});
}
async function startServer() {
try {
const { cert, key } = await generateCertificate();
const options = {
key: fs.readFileSync(key),
cert: fs.readFileSync(cert)
};
const server = https.createServer(options, (req, res) => {
let filePath = '.' + req.url;
if (filePath === './') {
filePath = './index.html';
}
const extname = String(path.extname(filePath)).toLowerCase();
const mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
};
const contentType = mimeTypes[extname] || 'application/octet-stream';
fs.readFile(filePath, (error, content) => {
if (error) {
if (error.code === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>404 - File Not Found</h1>', 'utf-8');
} else {
res.writeHead(500);
res.end('Error: ' + error.code, 'utf-8');
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
});
server.listen(PORT, () => {
console.log('\n' + '='.repeat(60));
console.log('🚀 Serveur HTTPS démarré avec succès!');
console.log('='.repeat(60));
console.log(`\n📱 Ouvrez Chrome et accédez à:`);
console.log(`\n 👉 https://localhost:${PORT}\n`);
console.log('⚠ Vous verrez un avertissement de sécurité (certificat auto-signé)');
console.log(' Cliquez sur "Avancé" puis "Continuer vers localhost"\n');
console.log('🔵 Web Bluetooth sera maintenant disponible!\n');
console.log('Appuyez sur Ctrl+C pour arrêter le serveur');
console.log('='.repeat(60) + '\n');
});
} catch (error) {
console.error('Impossible de démarrer le serveur:', error);
process.exit(1);
}
}
startServer();

76
server.py

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""
Serveur HTTPS simple pour Web Bluetooth API
Utilise un certificat auto-signé pour localhost
"""
import http.server
import ssl
import os
import subprocess
import sys
PORT = 8443
def generate_certificate():
"""Génère un certificat auto-signé si nécessaire"""
cert_file = 'cert.pem'
key_file = 'key.pem'
if os.path.exists(cert_file) and os.path.exists(key_file):
print('✓ Certificats existants trouvés')
return cert_file, key_file
print('Génération d\'un certificat auto-signé...')
try:
cmd = [
'openssl', 'req', '-newkey', 'rsa:2048', '-new', '-nodes',
'-x509', '-days', '365', '-keyout', key_file, '-out', cert_file,
'-subj', '/C=FR/ST=State/L=City/O=Organization/CN=localhost'
]
subprocess.run(cmd, check=True, capture_output=True)
print('✓ Certificat généré avec succès')
return cert_file, key_file
except (subprocess.CalledProcessError, FileNotFoundError):
print('\n Erreur: OpenSSL n\'est pas installé ou a échoué')
print('Installez OpenSSL: sudo apt install openssl')
sys.exit(1)
def main():
try:
cert_file, key_file = generate_certificate()
# Changer vers le répertoire du script
os.chdir(os.path.dirname(os.path.abspath(__file__)))
# Créer le serveur HTTP
handler = http.server.SimpleHTTPRequestHandler
httpd = http.server.HTTPServer(('localhost', PORT), handler)
# Configurer SSL
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain(cert_file, key_file)
httpd.socket = context.wrap_socket(httpd.socket, server_side=True)
print('\n' + '=' * 60)
print('🚀 Serveur HTTPS démarré avec succès!')
print('=' * 60)
print(f'\n📱 Ouvrez Chrome et accédez à:')
print(f'\n 👉 https://localhost:{PORT}\n')
print(' Vous verrez un avertissement de sécurité (certificat auto-signé)')
print(' Cliquez sur "Avancé" puis "Continuer vers localhost"\n')
print('🔵 Web Bluetooth sera maintenant disponible!\n')
print('Appuyez sur Ctrl+C pour arrêter le serveur')
print('=' * 60 + '\n')
httpd.serve_forever()
except KeyboardInterrupt:
print('\n\n✓ Serveur arrêté')
sys.exit(0)
except Exception as e:
print(f'\n❌ Erreur: {e}')
sys.exit(1)
if __name__ == '__main__':
main()

134
src/main.cpp

@ -0,0 +1,134 @@ @@ -0,0 +1,134 @@
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
// UUID pour le service et la caractéristique BLE
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Configuration des GPIO à surveiller
const int GPIO_PINS[] = {15, 16, 17, 18}; // GPIOs à surveiller
const int NUM_PINS = sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]);
// Variables globales
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
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 = "ESP32-Module-2";
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Client connecté");
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Client déconnecté");
}
};
void setup() {
Serial.begin(115200);
Serial.println("Démarrage du module ESP32 Bluetooth...");
// Configuration des GPIO en entrée avec pull-up
for (int i = 0; i < NUM_PINS; i++) {
pinMode(GPIO_PINS[i], INPUT_PULLUP);
}
// Initialisation BLE
BLEDevice::init(moduleName.c_str());
// Créer le serveur BLE
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Créer le service BLE
BLEService *pService = pServer->createService(SERVICE_UUID);
// Créer la caractéristique BLE
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
// Créer un descripteur BLE2902 pour les notifications
pCharacteristic->addDescriptor(new BLE2902());
// Démarrer le service
pService->start();
// Démarrer l'advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
pAdvertising->setMinPreferred(0x0);
BLEDevice::startAdvertising();
// Initialiser l'état des GPIO
for (int i = 0; i < NUM_PINS; i++) {
int pinState = digitalRead(GPIO_PINS[i]);
if (pinState == LOW) {
gpioStates |= (1 << i);
}
}
Serial.println("En attente de connexion...");
Serial.print("Nom du module: ");
Serial.println(moduleName);
}
void loop() {
// Lire l'état de toutes les GPIO
uint8_t currentStates = 0;
for (int i = 0; i < NUM_PINS; i++) {
int pinState = digitalRead(GPIO_PINS[i]);
if (pinState == LOW) { // GPIO à l'état bas
currentStates |= (1 << i);
}
}
// Créer un message JSON avec l'état des GPIO
String message = "{\"module\":\"" + moduleName + "\",\"gpios\":[";
for (int i = 0; i < NUM_PINS; i++) {
message += "{\"pin\":" + String(GPIO_PINS[i]) + ",\"state\":";
message += ((currentStates & (1 << i)) ? "1" : "0");
message += "}";
if (i < NUM_PINS - 1) message += ",";
}
message += "]}";
// Mettre à jour la valeur de la caractéristique
pCharacteristic->setValue(message.c_str());
// Si l'état a changé et qu'un client est connecté, envoyer notification
if (currentStates != gpioStates && deviceConnected) {
gpioStates = currentStates;
pCharacteristic->notify();
Serial.println("État changé: " + message);
}
// Gestion de la reconnexion
if (!deviceConnected && oldDeviceConnected) {
delay(500);
pServer->startAdvertising();
Serial.println("Redémarrage de l'advertising");
oldDeviceConnected = deviceConnected;
}
if (deviceConnected && !oldDeviceConnected) {
oldDeviceConnected = deviceConnected;
}
delay(100); // Délai pour éviter une lecture trop fréquente
}

79
start.sh

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
#!/bin/bash
# Script pour lancer Chrome avec Web Bluetooth activé sur Linux
echo "=========================================="
echo "Lancement de Chrome avec Web Bluetooth"
echo "=========================================="
echo ""
PORT=8443
URL="https://localhost:$PORT"
# Vérifier si le serveur tourne déjà
if ! curl -k -s "$URL" > /dev/null 2>&1; then
echo "📡 Démarrage du serveur HTTPS..."
python3 server.py &
SERVER_PID=$!
echo " Serveur PID: $SERVER_PID"
sleep 2
else
echo "✓ Serveur HTTPS déjà en cours d'exécution"
fi
echo ""
echo "🚀 Lancement de Chrome avec Web Bluetooth..."
echo ""
echo "⚠ Sur Linux, Web Bluetooth nécessite des flags expérimentaux"
echo ""
# Détecter le navigateur disponible
if command -v google-chrome &> /dev/null; then
CHROME="google-chrome"
elif command -v chromium &> /dev/null; then
CHROME="chromium"
elif command -v chromium-browser &> /dev/null; then
CHROME="chromium-browser"
else
echo "❌ Chrome/Chromium non trouvé!"
echo " Installez-le avec: sudo apt install chromium-browser"
exit 1
fi
echo "📌 Utilisation de: $CHROME"
echo ""
echo "🔵 Chrome va s'ouvrir avec les flags nécessaires pour Web Bluetooth"
echo ""
echo "Dans Chrome:"
echo " 1. Acceptez le certificat auto-signé (Avancé > Continuer)"
echo " 2. Cliquez sur 'Connecter un module'"
echo " 3. Sélectionnez votre ESP32"
echo ""
echo "Appuyez sur Ctrl+C pour tout arrêter"
echo "=========================================="
echo ""
# Lancer Chrome avec les flags nécessaires pour Web Bluetooth sur Linux
$CHROME \
--enable-features=WebBluetooth \
--enable-experimental-web-platform-features \
--enable-web-bluetooth-new-permissions-backend \
"$URL" \
2>/dev/null &
CHROME_PID=$!
# Fonction pour nettoyer à la sortie
cleanup() {
echo ""
echo "🛑 Arrêt du serveur..."
if [ ! -z "$SERVER_PID" ]; then
kill $SERVER_PID 2>/dev/null
fi
exit 0
}
trap cleanup SIGINT SIGTERM
# Attendre
wait $CHROME_PID

11
test/README

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
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