Bonsoir,
Voici un petit projet que j'ai commencé avec ESPHome dans Home assistant (HA).
Station météo - Do It Yourself / Vos Projets - Home Assistant Communauté Francophone (hacf.fr)
Le projet est devenu impossible à faire avec ESPHome et ça oblige de disposer de HA.
Donc me voici à chercher une solution pour faire communiquer plusieurs ESP ou Arduinos ensemble avec une consultation de données possible avec seulement une connexion internet (car maintenant je dois en faire une pour mon beau-père aussi ). Je suis tombé sur des tutos ESP-Now et j'ai trouvé LA solution.
Pour l'instant c'est un peu brouillon, je dois trouver un moyen de faire de mesurer la vitesse et la direction du vent et aussi la pluviométrie, en avoir un historique sur au moins 24h, tout envoyer vers MQTT car ça doit me servir dans HA et faire une page html propre avec les scripts dans un fichier à part. Toute aide ou aidée est la bienvenue .
Voici les différents codes mais d'ici peux je vais publier des repo public.
Emetteur extérieur
/*
Docstring à faire
*/
#include <Arduino.h>
#include <esp_now.h>
#include <esp_wifi.h>
#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <BH1750.h>
#include <arduino_secrets.h>
#include "MeteoFunctions.h"
#include "WeatherMeters.h"
#include <Ticker.h>
// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 1
Adafruit_BME280 bme; // I2C
BH1750 lightMeter;
MeteoFunctions calc;
// MAC Address of the receiver
uint8_t broadcastAddress[] = {0xC8, 0xC9, 0xA3, 0xCA, 0xEF, 0xB4};
// Structure example to send data
// Must match the receiver structure
typedef struct struct_message
{
int id;
float temp;
float hum;
float pres;
float lum;
float humidex;
float dewpoint;
float HI;
float cloudh;
float relpres;
float absolutehum;
float winddegrees;
float windspeed;
float rain;
int readingId;
} struct_message;
// Create a struct_message called myData
struct_message myData;
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
unsigned int readingId = 0;
// Insert your SSID
constexpr char WIFI_SSID[] = SECRET_WIFI_SSID;
// WeatherMeters
const int windvane_pin = 32;
const int anemometer_pin = 26;
const int raingauge_pin = 27;
volatile bool got_data = false;
Ticker ticker;
WeatherMeters<4> meters(windvane_pin, 8); // filter last 4 directions, refresh data every 8 sec
void ICACHE_RAM_ATTR intAnemometer()
{
meters.intAnemometer();
}
void ICACHE_RAM_ATTR intRaingauge()
{
meters.intRaingauge();
}
void secondCount()
{
meters.timer();
}
void readDone(void)
{
got_data = true;
}
// WeatherMeters end
esp_now_peer_info_t peerInfo;
int32_t getWiFiChannel(const char *ssid)
{
if (int32_t n = WiFi.scanNetworks())
{
for (uint8_t i = 0; i < n; i++)
{
if (!strcmp(ssid, WiFi.SSID(i).c_str()))
{
return WiFi.channel(i);
}
}
}
return 0;
}
// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
void setup()
{
// Init Serial Monitor
Serial.begin(115200);
Serial.println(__FILE__);
Wire.begin();
bool status;
// WeatherMeters
attachInterrupt(digitalPinToInterrupt(anemometer_pin), intAnemometer, FALLING);
attachInterrupt(digitalPinToInterrupt(raingauge_pin), intRaingauge, FALLING);
meters.attach(readDone);
ticker.attach(1.0, secondCount);
meters.reset(); // in case we got already some interrupts
// WeatherMeters end
status = lightMeter.begin(); // 0x23
if (!status)
{
Serial.println("Could not find a valid BH1750 sensor, check wiring!");
while (1)
;
}
status = bme.begin(0x76);
if (!status)
{
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1)
;
}
// Set device as a Wi-Fi Station and set channel
WiFi.mode(WIFI_STA);
int32_t channel = getWiFiChannel(WIFI_SSID);
WiFi.printDiag(Serial); // Uncomment to verify channel number before
esp_wifi_set_promiscuous(true);
esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
esp_wifi_set_promiscuous(false);
WiFi.printDiag(Serial); // Uncomment to verify channel change after
// Init ESP-NOW
if (esp_now_init() != ESP_OK)
{
Serial.println("Error initializing ESP-NOW");
ESP.restart();
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(OnDataSent);
// Register peer
// esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK)
{
Serial.println("Failed to add peer");
return;
}
}
void loop()
{
float temp;
float hum;
float pres;
float lum;
float humidex;
float dewpoint;
float HI;
float cloudh;
float relpres;
float absolutehum;
float winddegrees;
float windspeed;
float rain;
const float above_sea = 10;
/* // WeatherMeters
if (got_data)
{
got_data = false;
Serial.print("Wind degrees: ");
Serial.print(meters.getDir(), 1);
Serial.print(" Wind speed: ");
Serial.print(meters.getSpeed(), 1);
Serial.print("km/h, Rain: ");
Serial.print(meters.getRain(), 4);
Serial.println("mm");
}
delay(1); // or do something else
// WeatherMeters end */
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval)
{
// Save the last time a new reading was published
previousMillis = currentMillis;
// Set values to send
myData.id = BOARD_ID;
myData.temp = bme.readTemperature(), 1;
myData.hum = bme.readHumidity(), 0;
myData.pres = bme.readPressure() / 100.0F, 0;
myData.lum = lightMeter.readLightLevel(), 0;
myData.humidex = calc.humidex_c(bme.readTemperature(), bme.readHumidity()), 0;
myData.dewpoint = calc.dewPoint_c(bme.readTemperature(), bme.readHumidity()), 1;
myData.HI = calc.heatIndex_c(bme.readTemperature(), bme.readHumidity()), 0;
myData.cloudh = calc.cloudBase_m(bme.readTemperature(), bme.readHumidity()), 0;
myData.relpres = calc.relativePressure_c(bme.readPressure() / 100.0F, above_sea, bme.readTemperature()), 0;
myData.absolutehum = calc.absoluteHumidity_c(bme.readTemperature(), bme.readHumidity()), 0;
myData.winddegrees = meters.getDir(), 1;
myData.windspeed = meters.getSpeed(), 1;
myData.rain = meters.getRain(), 4;
myData.readingId = readingId++;
Serial.print("Temperature: ");
Serial.print(myData.temp);
Serial.println(" °C");
Serial.print("Humidity: ");
Serial.print(myData.hum);
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(myData.pres);
Serial.println(" hPa");
Serial.print("Luminosity: ");
Serial.print(myData.lum);
Serial.println(" lux");
Serial.print("Humidex: ");
Serial.println(myData.humidex);
Serial.print("Dew point: ");
Serial.print(myData.dewpoint);
Serial.println(" °C");
Serial.print("Heat index: ");
Serial.println(myData.HI);
Serial.print("Cloud height: ");
Serial.print(myData.cloudh);
Serial.println(" mètres");
Serial.print("Relative pressure: ");
Serial.print(myData.relpres);
Serial.println(" hPa");
Serial.print("Absolute humidity: ");
Serial.print(myData.absolutehum);
Serial.println(" g/m3");
Serial.print("Wind degrees: ");
Serial.print(myData.winddegrees, 1);
Serial.println(" °");
Serial.print("Wind speed: ");
Serial.print(myData.windspeed, 1);
Serial.println(" km/h");
Serial.print("Rain: ");
Serial.print(myData.rain, 4);
Serial.println("mm");
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(myData));
if (result == ESP_OK)
{
Serial.println("Sent with success");
}
else
{
Serial.println("Error sending the data");
}
}
}
// https://www.123calculus.com/calculer-pression-altitude-page-8-30-300.html
// https://www.omnicalculator.com/physics#s-16
// https://github.com/pilotak/MeteoFunctions/blob/master/examples/MeteoFunctions/MeteoFunctions.ino
// https://github.com/pilotak/WeatherMeters/blob/master/examples/WeatherMeters_external_ADC/WeatherMeters_external_ADC.ino
Recepteur (.cpp)
/*
Docstring à faire
*/
// Import required libraries
#include <Arduino.h>
#include <esp_now.h>
#include "WiFi.h"
#include "ESPAsyncWebServer.h"
#include "SPIFFS.h"
#include <ESPAsyncTCP/ESPAsyncTCP.h>
#include <Arduino_JSON.h>
#include <arduino_secrets.h>
// Replace with your network credentials (STATION)
const char *ssid = SECRET_WIFI_SSID;
const char *password = SECRET_WIFI_PASS;
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message
{
int id;
float temp;
float hum;
float pres;
float lum;
float humidex;
float dewpoint;
float HI;
float cloudh;
float relpres;
float absolutehum;
float winddegrees;
float windspeed;
float rain;
unsigned int readingId;
} struct_message;
struct_message incomingReadings;
JSONVar board;
AsyncWebServer server(90);
AsyncEventSource events("/events");
// callback function that will be executed when data is received
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int len)
{
// Copies the sender mac address to a string
char macStr[18];
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["pressure"] = incomingReadings.pres;
board["luminosity"] = incomingReadings.lum;
board["humidex"] = incomingReadings.humidex;
board["dewpoint"] = incomingReadings.dewpoint;
board["HI"] = incomingReadings.HI;
board["cloudh"] = incomingReadings.cloudh;
board["relpres"] = incomingReadings.relpres;
board["absolutehum"] = incomingReadings.absolutehum;
board["winddregrees"] = incomingReadings.winddegrees;
board["windspeed"] = incomingReadings.windspeed;
board["rain"] = incomingReadings.rain;
String jsonString = JSON.stringify(board);
events.send(jsonString.c_str(), "new_readings", millis());
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.1f \n", incomingReadings.temp);
Serial.printf("h value: %4.1f \n", incomingReadings.hum);
Serial.printf("p value: %4.0f \n", incomingReadings.pres);
Serial.printf("l value: %4.0f \n", incomingReadings.lum);
Serial.printf("hx value: %4.0f \n", incomingReadings.humidex);
Serial.printf("dp value: %4.1f \n", incomingReadings.dewpoint);
Serial.printf("HI value: %4.0f \n", incomingReadings.HI);
Serial.printf("ch value: %4.0f \n", incomingReadings.cloudh);
Serial.printf("rp value: %4.0f \n", incomingReadings.relpres);
Serial.printf("ah value: %4.1f \n", incomingReadings.absolutehum);
Serial.printf("wd value: %4.1f \n", incomingReadings.winddegrees);
Serial.printf("ws value: %4.1f \n", incomingReadings.windspeed);
Serial.printf("r value: %4.4f \n", incomingReadings.rain);
}
void setup()
{
// Serial port for debugging purposes
Serial.begin(115200);
// Initialize SPIFFS
if (!SPIFFS.begin(true))
{
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
// Set the device as a Station and Soft Access Point simultaneously
WiFi.mode(WIFI_AP_STA);
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
// Init ESP-NOW
if (esp_now_init() != ESP_OK)
{
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(OnDataRecv);
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(SPIFFS, "/index.html", String(), false); });
// Route to load style.css file
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(SPIFFS, "/style.css", "text/css"); });
events.onConnect([](AsyncEventSourceClient *client)
{
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000); });
server.addHandler(&events);
server.begin();
}
void loop()
{
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS)
{
events.send("ping", NULL, millis());
lastEventTime = millis();
}
}
Recepteur (index.html)
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Station météo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css"
integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<style>
html {
font-family: Arial;
display: inline-block;
text-align: center;
}
p {
font-size: 1.2rem;
}
body {
margin: 0;
}
.topnav {
overflow: hidden;
background-color: #2f4468;
color: white;
font-size: 1.7rem;
}
.darkmode {
overflow: hidden;
background-color: #2f4468;
color: white;
font-size: 1rem;
}
.content {
padding: 20px;
}
.card {
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
}
.cards {
max-width: 2000px;
margin: 0 auto;
display: grid;
column-gap: 2rem;
row-gap: 2rem;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.reading {
font-size: 2.8rem;
}
.timestamp {
color: #bebebe; font-size: 1rem;
}
.packet {
color: #bebebe;
}
.card.temperature {
color: #fd7e14;
}
.card.humidity {
color: #1b78e2;
}
.card.pressure {
color: #008000;
}
.card.luminosity {
color: #e6d300;
}
.card.dewpoint {
color: #fd7e14;
}
.card.humidex {
color: #1b78e2;
}
.card.HI {
color: #fd7e14;
}
.card.cloudh {
color: #bebebe;
}
.card.relpres {
color: #008000;
}
.card.absohum {
color: #1b78e2;
}
.card.winddegrees {
color: #6eb4f5;
}
.card.windspeed {
color: #6eb4f5;
}
.card.rain {
color: #bebebe;
}
.card.test {
color: #FFFFFF;
}
.dark {
background-color: #222;
color: #e6e6e6;
}
.mode {
float: right;
}
.change {
cursor: pointer;
border: 1px solid rgb(119, 15, 15);
border-radius: 40%;
width: 20px;
text-align: center;
padding: 5px;
margin-left: 8px;
}
.text-right {
text-align: right
}
</style>
</head>
<body>
<div class="topnav">
<h3>Station météo</h3>
</div>
<div class="darkmode">
<div class="switch">
<h5 class="text-right">Dark mode: <span class="inner-switch">OFF</span></h5>
</div>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<h4><i class="fas fa-thermometer-half"></i> Température extérieure</h4>
<p><span class="reading"><span id="t"></span> °C</span></p>
</div>
<div class="card humidity">
<h4><i class="fas fa-tint"></i> Humidité extérieure</h4>
<p><span class="reading"><span id="h"></span> %</span></p>
</div>
<div class="card pressure">
<h4><i class="fas fa-ruler-vertical"></i> Pression atmosphérique</h4>
<p><span class="reading"><span id="p"></span> hPa</span></p>
</div>
<div class="card luminosity">
<h4><i class="fas fa-star-of-life"></i> Luminosité extérieure</h4>
<p><span class="reading"><span id="l"></span> lux</span></p>
</div>
<div class="card dewpoint">
<h4><i class="fas fa-thermometer-half"></i> Point de rosée</h4>
<p><span class="reading"><span id="dp"></span> °C</span></p>
</div>
<div class="card humidex">
<h4><i class="fas fa-thermometer-half"></i> Humidex (Canada)</h4>
<p><span class="reading"><span id="hx"></span></span></p>
</div>
<div class="card HI">
<h4><i class="fas fa-thermometer-half"></i> Heat Index (USA)</h4>
<p><span class="reading"><span id="HI"></span></span></p>
</div>
<div class="card cloudh">
<h4><i class="fas fa-cloud"></i> Hauteur couverture nuageuse</h4>
<p><span class="reading"><span id="ch"></span> m</span></p>
</div>
<div class="card relpres">
<h4><i class="fas fa-ruler-vertical"></i> Pression atmosphériqu relative</h4>
<p><span class="reading"><span id="rp"></span> hPa</span></p>
</div>
<div class="card absohum">
<h4><i class="fas fa-tint"></i> Humiditée absolue</h4>
<p><span class="reading"><span id="ah"></span> g/m<sup>3</sup></span></p>
</div>
<div class="card winddegrees">
<h4><i class="fas fa-compass"></i> Direction vents degrés</h4>
<p><span class="reading"><span id="wd"></span> °</span></p>
</div>
<div class="card windspeed">
<h4><i class="fas fa-wind"></i> Vitesse vents</h4>
<p><span class="reading"><span id="ws"></span> km/h</span></p>
</div>
<div class="card rain">
<h4><i class="fas fa-cloud-rain"></i> Pluviométrie</h4>
<p><span class="reading"><span id="r"></span> mm</span></p>
</div>
</div>
</div>
<script>
$(document).ready(function () {
if (localStorage.getItem("mode") == "dark") {
$("body").addClass("dark");
$(".inner-switch").text("ON");
} else if (localStorage.getItem("mode") == "light") {
$("body").removeClass("dark");
$(".inner-switch").text("OFF");
}
var mq = window.matchMedia('(prefers-color-scheme: dark)');
if (localStorage.getItem("mode") == "light") {
$("body").removeClass("dark");
$(".inner-switch").text("OFF");
} else if (mq.matches) {
$("body").addClass("dark");
$(".inner-switch").text("ON");
}
});
$(".inner-switch").on("click", function () {
if ($("body").hasClass("dark")) {
$("body").removeClass("dark");
$(".inner-switch").text("OFF");
localStorage.setItem("mode", "light");
} else {
$("body").addClass("dark");
$(".inner-switch").text("ON");
localStorage.setItem("mode", "dark");
}
});
function getDateTime() {
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth() + 1) + "/"
+ currentdate.getFullYear() + " at "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
return datetime;
}
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function (e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function (e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function (e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function (e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t").innerHTML = obj.temperature.toFixed(1);
document.getElementById("h").innerHTML = obj.humidity.toFixed(1);
document.getElementById("p").innerHTML = obj.pressure.toFixed(0);
document.getElementById("l").innerHTML = obj.luminosity.toFixed(0);
document.getElementById("hx").innerHTML = obj.humidex.toFixed(0);
document.getElementById("dp").innerHTML = obj.dewpoint.toFixed(1);
document.getElementById("HI").innerHTML = obj.HI.toFixed(0);
document.getElementById("ch").innerHTML = obj.cloudh.toFixed(0);
document.getElementById("rp").innerHTML = obj.relpres.toFixed(0);
document.getElementById("ah").innerHTML = obj.absolutehum.toFixed(1);
document.getElementById("wd").innerHTML = obj.winddegrees.toFixed(1);
document.getElementById("ws").innerHTML = obj.windspeed.toFixed(1);
document.getElementById("r").innerHTML = obj.rain.toFixed(4);
document.getElementById("time").innerHTML = getDateTime();
}, false);
}
</script>
</body>
</html>