Capteur ouverture de porte (Contact Sensor)
Objectif
- Création d’un capteur d’ouverture de porte avec un ESP-01
- Intégration du capteur avec le Apple HomeKit
- Intégration du capteur avec l’application Pushover
- Aprendre les bases de arduino et de son IDE
- Création d’une REST API Simple
- Usage des modules ESP8266 (WiFi, HomeKit & WebServer)
matériel requis
Si vous achetez le matériel en utilisant les liens ci-dessous, je reçois une petite ristourne de Amazon
- 1x Résistance 10K Ohm Amazon.ca
- 1x Programmeur ESP-01 Amazon.ca
- 1x Module ESP-01 Amazon.ca
- 1x Capteurs de porte/fenêtre Amazon.ca
- 1x 5v à 3.3V (AMS1117) Amazon.ca
- 1x Bornier - 2 pins Amazon.ca
- 1x USB module Amazon.ca
Étapes
Étape 1 - Le code
Création du répertoire de travail,
.
|
|- accessories.c
|- handler.h
|- notification.h
|- spec.h
|- tools.h
|- main.ino
|- web.h
|- wifi_webux.h
Fichier | Description |
---|---|
accessories.c | Contient la définition pour Apple HomeKit |
handler.h | La logique pour gérer le status de la porte |
notification.h | les fonctions pour envoyer les notifications et mettre à jour le HomeKit |
spec.h | Toutes les variables et constantes |
tools.h | Logique pour calibrer le module selon le status actuelle |
main.ino | Le code pour que le module fonctionne |
web.h | La page web et le REST API |
wifi_webux.h | La logique pour configurer le WiFI |
Programmation
J’utilises Visual Studio Code et Arduino IDE
WIFI
Pour le WiFi, la configuration est générique.
- Connecter le wifi en utilisant les informations fournies
- Valider que le tout est disponible
wifi_webux.h
#ifndef WIFI_WEBUX_H_
#define WIFI_WEBUX_H_
// WiFi
const char *ssid = "votreSSID";
const char *password = "votreMotDePasse";
void ConnectWiFi()
{
WiFi.persistent(true);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(ssid, password);
while (!WiFi.isConnected())
{
delay(500);
}
}
void CheckWiFi()
{
if (WiFi.status() != WL_CONNECTED)
{
ConnectWiFi();
return;
}
}
#endif /* WIFI_WEBUX_H_ */
Serveur Web
Possède 3 fonctions,
- Gérer la page web
- Gérer le RestAPI
- Démarrer le serveur et écouter sur ‘/’ et ‘/api’
le fichier spec.h
est utilisé pour récupérer les informations du capteur
web.h
#ifndef WEB_H_
#define WEB_H_
#include "spec.h"
void handle_index()
{
String page = "<!DOCTYPE html><html>";
page += "<head>";
page += "<title>Door Sensor (" + LOCATION + ")</title>";
page += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
page += "<meta charset=\"UTF-8\">";
page += "<meta name=\"description\" content=\"Door Sensor Status\">";
page += "<meta name=\"author\" content=\"Studio Webux S.E.N.C\">";
page += "</head>";
page += "<body>";
page += "<h1>Door Status</h1>";
page += "<p>Status : <span><b>" + String(door_state) + "</b></span></p>";
page += "<p>" + SENSOR_NAME + " | " + LOCATION + "</p>";
page += "</body>";
page += "</html>";
server.send(200, "text/html", page);
}
void handle_api()
{
String JSON = "{";
JSON += "\"location\":\"" + LOCATION + "\",";
JSON += "\"sensor_name\":\"" + SENSOR_NAME + "\",";
JSON += "\"door_state\":\"" + String(door_state) + "\",";
JSON += "\"state\":\"" + String(state) + "\",";
JSON += "\"reminder\":\"" + String(reminder) + "\",";
JSON += "\"calibration\":\"" + String(calibration) + "\"";
JSON += "}";
server.send(200, "application/json", JSON);
}
void StartHTTPServer()
{
server.on("/", handle_index); //Handle Index page
server.on("/api", handle_api); //Handle API page
server.begin(); //Start the server
}
#endif /* WEB_H_ */
Notifications
Pour recevoir des ‘push notifications’ sur votre mobile.
il y a deux méthodes implémentées
- l’application Pushover
- Le Apple HomeKit
il est relativement simple de modifier ou d’ajouter d’autres méthodes.
La fonction
SendNotification(String message)
est générique, vous devriez être en mesure d’ajouter un service comme ‘IFTTT’ facilement.
#ifndef NOTIFICATION_H_
#define NOTIFICATION_H_
extern "C" homekit_characteristic_t cha_contact_sensor_state;
void SendNotification(String message)
{
const int LEN = message.length();
CheckWiFi();
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort))
{
return;
}
client.print(String("POST ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + LEN + "\r\n\r\n" +
message);
unsigned long timeout = millis();
while (client.available() == 0)
{
if (millis() - timeout > 5000)
{
client.stop();
return;
}
}
}
void NotifyHomeKit(int _state)
{
cha_contact_sensor_state.value.uint8_value = _state;
homekit_characteristic_notify(&cha_contact_sensor_state, cha_contact_sensor_state.value);
}
#endif /* NOTIFICATION_H_ */
Handler
Cette fonction fait que changer la valeur du status de la porte selon la lecture du capteur. Elle est configurée pour utiliser un ‘Interrupt’ (Voir le main.c)
handler.h
#ifndef HANDLER_H_
#define HANDLER_H_
#include "spec.h"
ICACHE_RAM_ATTR void changeDoorStatus()
{
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval)
{
previousMillis = currentMillis;
previousReminder = currentMillis;
state = !state;
if (state)
{
door_state = "opened";
}
else
{
door_state = "closed";
}
flag = true;
}
}
#endif /* HANDLER_H_ */
Outils
J’ai remarqué quelques incohérences dans certain cas. Alors cette fonction s’assure que la valeure retournée est toujours vrai selon la lecture du capteur.
Remarque, Gardez en tête que le bug le plus courant est la lecture du capteur, c’est-à-dire que si vous avez un sensor de type NC (Normally closed), il va réagir d’une certaine manière et si vous un capteur de type NO (Normally Opened) il va réagir de la manière inverse. Tout ça pour dire qu’il se peut que vous devriez inverser toute la logique du code …
tools.h
#ifndef TOOLS_H_
#define TOOLS_H_
#include "spec.h"
unsigned long previousCalibration = 0;
void Calibration()
{
unsigned long now = millis();
// Check the physical status of the door when calibration is triggered
if (now - previousCalibration >= calibration)
{
const int s = digitalRead(pin);
previousCalibration = millis();
// Reversed because we are using a NC sensor
// With NC sensor
if (s == 1)
{
door_state = "closed";
state = 0;
}
else
{
door_state = "opened";
state = 1;
}
}
}
#endif /* TOOLS_H_ */
Apple HomeKit
Le fichier accessories.h
a une structure très spécifique et quelques standards à respecter.
Je vous recommande une lecture de la documentation de Apple pour bien comprendre les requis, puis d’aller voir le code du projet : Arduino-HomeKit-ESP8266 Pour mieux comprendre se que la librairie s’attend comme valeurs
En résumé ce fichier couvre le capteur de porte (Door Sensor). La manière dont j’ai réussi à le faire fonctionner pour deux portes différentes est :
- Changer le numéro de série (
HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "00000002"),
) - Changer le nom du capteur (
HOMEKIT_CHARACTERISTIC(NAME, "Door Sensor 00000002"),
) - Puis pour des raisons de sécurité, changer le mot de passe (
.password = "123-45-678",
)
accessories.h
#include <homekit/homekit.h>
#include <homekit/characteristics.h>
// Called to identify this accessory. See HAP section 6.7.6 Identify Routine
// Generally this is called when paired successfully or click the "Identify Accessory" button in Home APP.
void my_accessory_identify(homekit_value_t _value) {
printf("accessory identify\n");
}
/**
Defines that the accessory contains a contact sensor.
Required Characteristics:
- CONTACT_SENSOR_STATE
Optional Characteristics:
- NAME
- STATUS_ACTIVE
- STATUS_FAULT
- STATUS_TAMPERED
- STATUS_LOW_BATTERY
*/
homekit_characteristic_t cha_contact_sensor_state = HOMEKIT_CHARACTERISTIC_(CONTACT_SENSOR_STATE, 0);
homekit_characteristic_t cha_status_active = HOMEKIT_CHARACTERISTIC_(STATUS_ACTIVE, 1);
homekit_characteristic_t cha_status_fault = HOMEKIT_CHARACTERISTIC_(STATUS_FAULT, 0);
homekit_characteristic_t cha_name = HOMEKIT_CHARACTERISTIC_(NAME, "Door Sensor");
homekit_accessory_t *accessories[] = {
HOMEKIT_ACCESSORY(.id=1, .category=homekit_accessory_category_sensor, .services=(homekit_service_t*[]) {
HOMEKIT_SERVICE(ACCESSORY_INFORMATION, .characteristics=(homekit_characteristic_t*[]) {
HOMEKIT_CHARACTERISTIC(NAME, "Door Sensor 00000002"),
HOMEKIT_CHARACTERISTIC(MANUFACTURER, "Studio Webux S.E.N.C"),
HOMEKIT_CHARACTERISTIC(SERIAL_NUMBER, "00000002"),
HOMEKIT_CHARACTERISTIC(MODEL, "ESP-01S"),
HOMEKIT_CHARACTERISTIC(FIRMWARE_REVISION, "1.0"),
HOMEKIT_CHARACTERISTIC(IDENTIFY, my_accessory_identify),
NULL
}),
HOMEKIT_SERVICE(CONTACT_SENSOR, .primary=true, .characteristics=(homekit_characteristic_t*[]) {
&cha_contact_sensor_state,
&cha_status_active,
&cha_status_fault,
&cha_name,
NULL
}),
NULL
}),
NULL
};
homekit_server_config_t config = {
.accessories = accessories,
.password = "123-45-678",
//.on_event = on_door_event,
//.setupId = "ABCD"
};
Spécifications
Contient toutes les variables et constantes
spec.h
#ifndef SPEC_H_
#define SPEC_H_
// Sensor Details
const String SENSOR_NAME = "ESP01";
const String LOCATION = "Front Door";
// Hardware
const int pin = 2;
// Notifications
const char *host = "api.pushover.net";
const String apiKey = "REPLACEME";
const String userKey = "REPLACEME";
const String url = "/1/messages.json";
// State
char *door_state = "closed"; // or opened
volatile int state = false; // 0 == closed | 1 == opened
volatile int flag = false;
String message = "";
// Timer
const long interval = 3000;
const long reminder = 900000; //15 Minutes
const long calibration = 30000; // 30 seconds
const long reportInterval = 10000;
// Timer Variable
unsigned long previousMillis = 0;
unsigned long previousReminder = 0;
unsigned long reportMillis = 0;
// Web Server
ESP8266WebServer server(80);
#endif /* SPEC_H_ */
Main
Implémentation en NC
main.ino
/*
Studio Webux S.E.N.C 2020-09-28
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <arduino_homekit_server.h>
#include "wifi_webux.h"
#include "web.h"
#include "spec.h"
#include "notification.h"
#include "tools.h"
#include "handler.h"
extern "C" homekit_server_config_t config;
void setup()
{
Serial.begin(115200);
delay(100);
pinMode(pin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(pin), changeDoorStatus, CHANGE);
ConnectWiFi();
message = "user=" + userKey +
"&token=" + apiKey +
"&message=" + SENSOR_NAME + ": Powered ON (" + WiFi.localIP().toString() + ")\r\n";
SendNotification(message);
StartHTTPServer();
arduino_homekit_setup(&config);
}
void loop()
{
arduino_homekit_loop();
unsigned long now = millis();
// //Handling of incoming client requests
server.handleClient();
Calibration();
if (now - reportMillis >= reportInterval)
{
NotifyHomeKit(state);
reportMillis = now;
}
// If door has changed
if (flag)
{
NotifyHomeKit(state);
// Pushover structure
message = "user=" + userKey +
"&token=" + apiKey +
"&message=" + SENSOR_NAME + ": " + door_state + " door at " + LOCATION + "\r\n";
SendNotification(message);
flag = false;
}
else
{
// if door is opened for > {reminder}
if (state)
{
if (now - previousReminder >= reminder)
{
previousReminder = now;
// Pushover structure
message = "user=" + userKey +
"&token=" + apiKey +
"&message=" + SENSOR_NAME + ": Door still opened at " + LOCATION + "\r\n";
SendNotification(message);
}
}
}
delay(1000);
}
Étape 2 - Assemblage du programmeur
Pour être en mesure de programmer le code sur le ESP-01,
Arduino IDE
Vous devez installer le module Arduino ESP8266 et le ESP8266 HomeKit et les autres dépendences
Voici ma configuration pour que le tout fonctionne,
Le programmeur
Pour mettre le esp-01 en mode programmation vous devez connecter la pin IO0 et GND ensemble. Vous pouvez souder un bouton sur le programmeur ou ajouter deux fils et les brancher ensemble pendant la connexion USB.
Étape 3 - Assemblage
Voici le schéma:
Améliorations
- Intégrer OTA
- Compléter le serveur web pour permettre de configurer le capteur de manière dynamique
- Ajouter une route optionel pour connecter d’autres services pour les notifications