Webux Lab

Par Studio Webux

Capteur de porte

TG
Tommy Gingras Studio Webux S.E.N.C 2020-11-22

Capteur ouverture de porte (Contact Sensor)

Objectif

matériel requis

Si vous achetez le matériel en utilisant les liens ci-dessous, je reçois une petite ristourne de Amazon

É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.

  1. Connecter le wifi en utilisant les informations fournies
  2. 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,

  1. Gérer la page web
  2. Gérer le RestAPI
  3. 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

  1. l’application Pushover
  2. 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 :

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

Sources


Recherche