Ayé, J’ai fini…
Du coup, beaucoup de boulot pour réussir à faire ce que je voulais. Pouvoir utiliser le BLE advertisement pour envoyer des infos custom et les recevoir / décoder dans Jeedom via le plugin gratuit BLEA.
Le projet se compose de 5 fichiers en tout :
- ESP32.ino qui est le fichier à transférer sur l’ESP32
- ESP32.py qui est le fichier BLEA qui fait le tri des infos reçues en données lisibles par Jeedom
- ESP32.json qui est le fichier de config BLEA qui défini les objets de type ESP32 et les infos que Jeedom/BLEA doit récupérer / afficher / historiser
- BLEAdvertising.cpp et son pendant BLEAdvertising.h qui sont 2 fichiers issus de la librairie ESP32 pour faire de l’IOT BLE, mais auxquels ils manquaient une fonction que j’ai du rajouter pour les besoins du code.
ESP32.INO :
Ce fichier est un sketch assez classique pour arduino et ESP32. On y déclare les paramètres assez classiques pour du BLE (Serveur, advertising data, variables à transmettre). Les data sont fournies sous forme de chaine de caractères via la fonction sprintf(…) pour avoir les infos (entiers) sur 3 digits à la suite. (Le code est assez logique à comprendre quand on connait un peu arduino)
ATTENTION ! Ce code utilise une fonction que vous ne trouverez nul par ailleurs sur le net (pour l’instant ) voir plus loin les explications.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
BLEAdvertising *pAdvertising;
BLEAdvertisementData advData;
// cData Structure
// CHAR - Value - Description
//-------------------------------------
// 01-03 xxx 10x(temp+50) in °C (683 > (883-500)/10 = 18.3°C) [0>999 = -50>49.9°C]
// 04-06 xxx 10x humidity in % (475 > 47.5%) [0>999 = 0>99.9%]
// 07-09 xxx 10x windspeed in m/S (065 > 6.5 m/s) [0>999 = 0>99.9m/s : 359 km/h]
// 10-12 xxx 1x windOrigin in ° (015 > 15.0° = NNE)
// 13-15 xxx 10x rainfall in mm (121 > 12.1 mm)
float i=0;
float temp=683;
float humi=475;
float wnds=065;
float wndd=015;
float rain=121;
char result[15]; // 5 parameters * 3 "digits" = 15 chars
void setup() {
Serial.begin(115200);
BLEDevice::init("ESP32"); // Create the BLE Device
pServer = BLEDevice::createServer(); // Create the BLE Server
}
void loop() {
// Compute data to send
i+=0.1;
int temp2=temp*10*cos(i); // this is a custom function to get the values to be sent
int humi2=humi*10*cos(i); // Replace this part with your custom way to get data
int wnds2=wnds*10*cos(i);
int wndd2=wndd*10*cos(i);
int rain2=rain*10*cos(i);
// Format the values in triplets of digits (%03d)
sprintf(result, "%03d%03d%03d%03d%03d", temp2, humi2, wnds2, wndd2, rain2);
// create advertising data set (clean at each exec of loop)
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
advData.clearAdvertisementData();
advData.setFlags(0x06);
advData.setServiceData(uint16_t(0xFE95), result); // 95fe 363833343735303635303135313231
// 6 8 3 4 7 5 3 6 5 0 1 5 1 2 1
// each char in the data is replaced by his HEX value (ASCII) (0-9 : 30-39 _ a-z : 61-7a - A-Z : 41-5a)
// Set advertisement data
pAdvertising->setAdvertisementData(advData);
pAdvertising->start();
delay(5000);
}
esp32.json
Fichier assez classique pour qui bricole dans BLEA. A copier > coller via JeeXplorer dans :
(attention aux majuscules_minuscules
plugins \ blea \ core \ config \ devices \ esp32 (à créer) \ exp32.json
Vous pouvez rajouter une photo de votre choix et la nommer esp32.jpg
Là, c’est assez simple, on définit les différentes infos que Jeedom doit récupérer à partir du fichier python. (L’important c’est que les logicalId de ce fichier correspondent aux action[« variable »] = valeur du fichier esp32.py)
{
"esp32": {
"name": "esp32",
"groupe" : "Capteurs",
"configuration" : {
"needsrefresh" : 0,
"name" : "esp32",
"battery_type" : "1x3V CR2032",
"delay" : 900,
"cancontrol" : 0
},
"commands": [
{
"name": "Température",
"type": "info",
"subtype": "numeric",
"display": {
"icon": "<i class=\"fas fa-thermometer-empty\"><\/i>",
"generic_type": "DONT"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "°C",
"logicalId": "temperature",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "Humidité",
"type": "info",
"subtype": "numeric",
"display": {
"icon": "<i class=\"fas fa-tint\"><\/i>",
"generic_type": "DONT"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "%",
"logicalId": "moisture",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "Batterie",
"type": "info",
"subtype": "numeric",
"display": {
"icon": "<i class=\"fas fa-battery-full\"><\/i>",
"generic_type": "DONT"
},
"isVisible": 0,
"isHistorized": 0,
"unite": "%",
"logicalId": "battery",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "WindSpeed",
"type": "info",
"subtype": "numeric",
"display": {
"generic_type": "GENERIC"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "m/s",
"logicalId": "windspeed",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "WindDirection",
"type": "info",
"subtype": "numeric",
"display": {
"generic_type": "GENERIC"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "°",
"logicalId": "winddirection",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "RainFall",
"type": "info",
"subtype": "numeric",
"display": {
"generic_type": "GENERIC"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "m/s",
"logicalId": "rainfall",
"template": {
"dashboard": "line",
"mobile": "line"
}
},
{
"name": "Refresh",
"type": "action",
"subtype": "other",
"display": {
"generic_type": "GENERIC"
},
"isVisible": 1,
"isHistorized": 0,
"unite": "",
"logicalId": "refresh"
}
],
"compatibility": [
{
"manufacturer": "esp32",
"name": "Température Humidité",
"doc": "",
"type": "Capteurs",
"battery_type": "1x3V CR2032",
"ref" : "",
"comlink": "",
"remark": "Capteurs perso a base dun ESP32",
"inclusion" : "NA",
}
]
}
}
esp32.py
Fichier assez classique pour qui bricole dans BLEA. A copier > coller via JeeXplorer dans :
(attention aux majuscules_minuscules
plugins \ blea \ ressources \ blead \ devices \ exp32.py
Ici, c’est un simple décodage des données qui sont découvertes par le daemon BLEA. La feinte se situe dans la découverte des données par le plugin. La function isvalid() s’appuie sur le début de l’adresse MAC de votre ESP32 (rien de bien dur à changer ou à trouver avec une appli cliente BLE). La partie data.lower().startswith("95fe")
correspond à une inversion des octets de la partie déclarée dans le fichier esp32.ino advData.setServiceData(uint16_t(0xFE95), result);
FE95 <> 95fe.
Si vous changez l’un, changez l’autre.
Le reste est assez classique en fait
# coding: utf-8
from bluepy import btle
import time
import logging
import globals
import struct
from multiconnect import Connector
from notification import Notification
class esp32():
def __init__(self):
self.name = 'esp32'
self.ignoreRepeat = False
def isvalid(self,name,manuf='',data='',mac=''):
logging.debug('esp32------isvalid data=%s, mac=%s, name=%s, manuf=%s' % (data, mac, name, manuf))
if name.lower() in [self.name]:
return True
if data.lower().startswith("95fe") or (mac.lower().startswith("ac:67:b2")):
#broadcasted advertising data
return True
def parse(self,data,mac,name,manuf):
logging.info('esp32------adv data=%s, mac=%s, name=%s, manuf=%s' % (data, mac, name, manuf))
action={}
action['present'] = 1
temp = (int(bytes.fromhex(data[4:6])+bytes.fromhex(data[6:8])+bytes.fromhex(data[8:10]))-400)/10
humi = int(bytes.fromhex(data[10:12])+bytes.fromhex(data[12:14])+bytes.fromhex(data[14:16]))/10
wnds = int(bytes.fromhex(data[16:18])+bytes.fromhex(data[18:20])+bytes.fromhex(data[20:22]))/10
wndd = int(bytes.fromhex(data[22:24])+bytes.fromhex(data[24:26])+bytes.fromhex(data[26:28]))
rain = int(bytes.fromhex(data[28:30])+bytes.fromhex(data[30:32])+bytes.fromhex(data[32:34]))/10
#logging.info('ESP32-------temp=%s, humi=%s, wnds=%s, wndd=%s, rain=%s' % (temp, humi, wnds, wndd, rain))
action["temperature"] = temp
action["moisture"] = humi
action["windspeed"] = wnds
action["winddirection"] = wndd
action["rainfall"] = rain
logging.info('ESP32-------mac=%s, temp=%s, humi=%s, wnds=%s, wndd=%s, rain=%s' % (mac, temp, humi, wnds, wndd, rain))
#logging.info('esp32------mac=%s, temp=%s, humi=%s, batt=%s' % (mac, temp,humi,batt))
return action
def read(self,mac):
result={}
try:
conn = Connector(mac)
conn.connect()
if not conn.isconnected:
conn.connect()
if not conn.isconnected:
return
batt = bytearray(conn.readCharacteristic('0x3a'))
battery = batt[0]
notification = Notification(conn,drkamp)
notification.subscribe(10)
result['battery'] = battery
result['id'] = mac
logging.debug('esp32test------'+str(result))
return result
except Exception as e:
logging.error(str(e))
return result
def handlenotification(self,conn,handle,data,action={}):
result={}
if hex(handle) == '0x36':
received = bytearray(data)
temperature = float(received[1] * 256 + received[0]) / 100
moisture = received[2]
result['moisture'] = moisture
result['temperature'] = temperature
result['windspeed'] = temperature
result['winddir'] = temperature
result['rainfall'] = moisture
result['id'] = conn.mac
result['source'] = globals.daemonname
globals.JEEDOM_COM.add_changes('devices::'+conn.mac,result)
globals.COMPATIBILITY.append(esp32)
BLEAdvertising.cpp et BLEAdvertising.h
BLEAdvertising.cpp.pdf (16,5 Ko) BLEAdvertising.h.pdf (2,9 Ko)
Notes, les fichiers sont à récupérer en enlevant le .pdf bien sûr.
Ce sont les fichiers que j’ai du bidouiller un peu (pas grand chose) car il me manquait une fonction. Si votre fichier esp32.ino ne compile pas, c’est peut-être à cause de çà. Donc il faut remplacer les 2 fichiers fournis en faisant une copie des originaux. (Attention, vous ne pouvez pas les laisser dans le même dossier. J’ai tenté, j’ai eu 2000 lignes d’erreur à la compilation )
En gros, je n’ai fais qu’une chose, c’est rajouté une fonction qui vide les données qui sont « advertisées » . En décortiquant les fonctions liées au advertisingData
comme setFlags(),
ou setName()
on voit que les infos sont « ajoutée » et non pas définies et donc remplacées si on appelle la fonction une 2ème fois. A force de rajouter des infos, les données dépassent le nombre de bytes autorisé et KABOOM. C’est pourquoi il faut vider les datas et c’est ce que j’ai fait avec la fonction toute con clearAdvertisingData()
qui fait en gros : payload=""
. (trop dur !!!)
Voila, SI vous avez tout lu, BRAVO !
Je reste dispo si vous avez des questions car j’ai bien kiffé plonger dans les tréfonds du plugin
++ tout le monde
PS : C:\Users[USER]\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.6\libraries\BLE\src