Fensterkontakt und Integration ins Smart Home

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Einleitung

Es gibt schon unzählige Fensterkontakte und dies ist ein weiterer. Meine Motivation für dieses IoT-Projekt war, ein Projekt von der Idee bis zum fertigen Produkt inklusive Design einer Platine und Konstruktion eines Gehäuses zu durchzuziehen.

Ich habe ein Smart Home auf KNX-TP-Basis. Fensterkontakte auf KNX-RF-Basis, über WLAN, ZigBee usw. gibt es. Allerdings sind sie recht groß und teuer. Ich habe nun an jedem Fensterflügel einen selbstgebauten Fensterkontakt installiert. Zur Zeit visualisiere ich damit, welche Fenster offen sind, gebe einen Alarm aus, wenn mindestens ein Fenster offen ist und keine Anwesenheit der Wohnungsbewohner erkannt wird oder schalte die Wohnraumlüftungsanlage ab, wenn mindestens 2 Fenster offen sind.

Übersicht

Bild1 Fensterkontakt.jpg

Der Fensterkontakt ist mit einem handelsüblichen IoT-Controller ESP12F von Espressiv aufgebaut. Über einen Reed-Kontakt und einem kleinen Magneten wird der Fensterzustand detektiert. Bei einer Änderung des Zustands wacht der ESP12F auf und verbindet sich über WLAN mit einem MQTT-Server. Die Datenpunkte des MQTT-Servers verwendet ioBroker zur Visualisierung und Steuerung. Danach geht der ESP12F in den Deep Sleep Modus, um Strom zu sparen. Der verwendete 500mAh-LiPo sollte für ein Jahr reichen.

Zu einem späteren Zeitpunkt ist geplant, den darüber hinaus zur Visualisierung und Steuerung der KNX-Komponenten vorhandenen Domovea-Server TJA450 durch den IoT-Server TJA670 zu ersetzen. Hier könnte der Fensterkontakt über IFTTT eingebunden werden. Das wäre dann lediglich eine Softwareänderung auf dem ESP12F.

Hardware

Schaltplan Fensterkontakt.png

Schaltplan und Platinenlayout wurden mit KiCAD erstellt. Eine sehr gute Einführung siehe unter Quellen.

Am Konnektor J1 wird an VCC/GND der LiPo angeschlossen. Bei einer Programmänderung wird der Fensterkontakt über die serielle Schnittstelle J1 an einen USB-Programmer angeschlossen. Dieser übernimmt dann auch die Spannungsversorgung. Der ESP12F benötigt eine Spannung von 3.3V. Diese wird mit dem MCP1700-3302 erzeugt. Mit der einen Hälfte des 74HC86 (U3A und U3B) wird ein Flankendetektor realisiert. Beim Öffnen oder Schließen des Reedkontakts SW2 wird so ein Impuls erzeugt, der den ESP12F über seinen Reset-Eingang RSTn aufweckt. Die beiden anderen Gatter des 74HC86 (U3C und U3D) werden nicht benötigt. Deren Eingänge müssen jedoch an ein definiertes Potential gelegt werden, damit die Gatter nicht schwingen und damit im Deep Sleep Modus des ESP12F den Stromverbrauch von ca. 30uA auf 60uA erhöhen, was natürlich die Akkulaufzeit wesentlich mindert. Über die DIP-Schalter SW3 kann der Fensterkontakt codiert werden, so dass 16 verschiedene Kontakte unterscheidbar sind.

Platine und Stückliste

Layout Fensterkontakt.png

Platine Fensterkontakt V2.jpg

Die Platine ist mit 2 Layern aufgebaut. Die Gerberdaten stelle ich unter Downloads zur Verfügung. Damit kann die Platine bei den entsprechenden Dienstleistern bestellt werden. Gute Erfahrungen habe ich mit JLCPCB.COM aus China gemacht. Ein Satz Platinen (Mindestbestellmenge z.Zt. 5 Stück) kostet im einstelligen Euro-Bereich und die Lieferzeit beträgt mit Standardlieferung ca. 2-3 Wochen (Anm.: dies ist zwar Werbung, ich erhalte keinerlei Vergütung dafür). Der Aufbau verwendet SMD-Bauteile; wer ein wenig Übung hat, wird aber keine Probleme damit haben.

Stückliste:

  • 1x Platine Fensterkontakt_V2
  • 1x ESP12F
  • 1x MCP1700-3302, SOT23
  • 1x 74HC86, SOP14
  • 1x Meder MK06-6-B
  • 2x 1uF, SMD1206
  • 1x 100nF, SMD1206
  • 1x 1MOhm, SMD1206
  • 1x 10kOhm, SMD1206
  • 1x Taster 1polig, SMD
  • 1x 4-fach DIP-Schalter, SMD
  • 1x Pfostensteckverbinder 4polig, 90°
  • 2x Stabmagnet 19,5x2,75x1,75mm3 (es kann aber jeder andere verwendet werden; passt dann nur nicht in mein kleines Gehäuse)
  • 1x LiPo 3,7V 721855
  • 1x Gehäusesatz

Die Bauteile habe ich alle über Aliexpress in China bestellt (Anm.: dies ist zwar Werbung, ich erhalte keinerlei Vergütung dafür).. Das kann aber bis zu 8 Wochen dauern. Bisher bin ich in China nur bei den Spannungsreglern reingefallen. Die paar Cent erhält man zurück, aber die Zeit für Fehlersuche und erneuter Bestellung ist futsch. Bei einem großen Elektronikversender aus Deutschland ist mir allerdings beim Spannungsregler dasselbe passiert. Eine Reaktion auf die Reklamation erfolgte hierbei jedoch nicht.

Software

Das Programm für den ESP12F wurden mit der Arduino-Entwicklungsumgebung erstellt. Alternativ verwende ich auch Visual Studio Code in Verbindung mit PlatformIO.

/*
   File: siehe const char SOFTWARE_VERSION
   
   Zielsystem: ESP12F

      ACHTUNG: unbedingt während des Programmierens den PIN D3/FLASH auf GND legen
      
      Verbinden mit WLAN
      Einlesen des Fensterkontakts an Pin D2/GPIO4
      Benennung des Fensterkontakts mit 4 DIP-Schaltern an D7/GPIO13, D6/GPIO12, D5/GPIO14, D1/GPIO5
           - bei allen Eingängen wird der interne Pullup aktiviert, d.h. unbeschaltet = Kontakt_0
           - alle extern über DIL-Schalter auf GND geschaltet, d.h. = Kontakt_15
      Ausgabe Status über serielle Schnittstelle - Problem: undefinierte Zeichenausgabe nach Aufwachen aus deep sleep mode
      Ausgabe über WLAN
      Ausgabe über MQTT
      deep sleep mode
      Unterspannungserkennung an VCC, Pin ADC darf nicht beschaltet werden

      v5: Topics werden in ein Verzeichnis fensterkontakte geschrieben
      v6: Energiesparen: nur maximal x Verbindungsversuche WLAN und MQTT-Server
      V7: Fehler im DeepSleep-Mode in Funktion goto_Deepsleep behoben
      V8: spannungmin von 3100 auf 3300 geändert
*/

//////////////////////////////////////////////////////////
const char* SOFTWARE_VERSION = "Fensterkontakt_ESP12_v8";
//////////////////////////////////////////////////////////


#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

#define TUERKONTAKT D2
#define BIT3 D7
#define BIT2 D6
#define BIT1 D5
#define BIT0 D1

#define MAX_TRIALS_WLAN 20
#define MAX_TRIALS_MQTT 10

// Constants
const char* ssid = "euerNetzwerkname";                         // WiFi ssd
const char* password = "euerPasswort";                        // WiFi password
const char* mqtt_server = "eureIPAdressevomMQTTBroker";       // IP-Adresse des Brokers (meines Raspis mit iobroker)

WiFiClient espClient;
PubSubClient client(espClient);

// Variables
char ID[50];                                        // eindeutiger Name des Moduls für MQTT-Übertragung
char USPG[50];                                      // für MQTT-Übertragung Unterspannung
int kontaktnummer;
char buffer[20];
int buttonstate = 0;
int spannung;
// minimale Spannung am ESP12, bei der die Sendeleistung vom WLAN noch ausreicht
int spannungmin = 3300; // entspricht einer gemessenen Spannung Vdd=3,3V 
int zaehler_MQTT = 0;
int zaehler_WLAN = 0;

ADC_MODE(ADC_VCC);

void setup_wifi() {
    
    delay(100);
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) 
    {
      delay(500);
      Serial.print(".");

      zaehler_WLAN += 1;
      Serial.print("zaehler_WLAN = ");
      Serial.println(zaehler_WLAN);
      if (zaehler_WLAN == MAX_TRIALS_WLAN) {
        Serial.println("maximale Anzahl Versuche, sich mit WLAN zu verbinden, erreicht");
        goto_deepsleep();
      }
    }
    randomSeed(micros());
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
}

void reconnect() 
{
  // Loop until we're reconnected
  while (!client.connected()) 
  {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    // if you MQTT broker has clientID,username and password
    // please change following line to    if (client.connect(clientId,userName,passWord))
    if (client.connect(clientId.c_str()))
    {
      Serial.println("connected");
     //once connected to MQTT broker, subscribe command if any
      client.subscribe("MyCommand");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 6 seconds before retrying

       zaehler_MQTT += 1;
      Serial.print("zaehler_MQTT = ");
      Serial.println(zaehler_MQTT);
      if (zaehler_MQTT == MAX_TRIALS_MQTT) {
        Serial.println("maximale Anzahl Versuche, sich mit MQTT-Server zu verbinden, erreicht");
        goto_deepsleep();
      }
      
      delay(6000);
    }
  }
}

void setup() {

  // Initialisierung der seriellen Schnittstelle
  Serial.begin(115200);

  Serial.println();
  Serial.print("Software-Version: ");
  Serial.println(SOFTWARE_VERSION);
  Serial.println();

  // Türkontakt
  pinMode(TUERKONTAKT, INPUT);

  // Türkontakt-Codierung
  pinMode(BIT3, INPUT_PULLUP);
  pinMode(BIT2, INPUT_PULLUP);
  pinMode(BIT1, INPUT_PULLUP);
  pinMode(BIT0, INPUT_PULLUP);

  // Vorbelegung der Strings
  ID[0] = 0;
  strcat(ID, "fensterkontakte/kontakt_");
  USPG[0] = 0;
  strcat(USPG, "fensterkontakte/kontakt_");

  // Vervollständigung der Strings in Abhängigkeit der DIP-Schalter
  kontaktnummer = !digitalRead(BIT3)*8 + !digitalRead(BIT2)*4 + !digitalRead(BIT1)*2 + !digitalRead(BIT0)*1;
  strcat(ID, itoa(kontaktnummer, buffer, 10));
  strcat(ID, "_status");
  strcat(USPG, itoa(kontaktnummer, buffer, 10));
  strcat(USPG, "_unterspannung");
  Serial.println("");
  Serial.println(ID);
  Serial.println(USPG);

    // Initialisierung WLAN
  setup_wifi();

  // Initialisierung MQTT Server
  client.setServer(mqtt_server, 1883);
 
}

void goto_deepsleep() {
  // gehe in den deep sleep mode, Aufwachen über RST Pin
    delay(1000);
    Serial.println("...gehe nun schlafen...");
    ESP.deepSleep(0);
    delay(1000);
    Serial.println("....schlafe");
    Serial.println();   
}

void loop() {

    if (!client.connected()) {
      reconnect();
    }
    client.loop();

    // delay(5000);

    Serial.print("Fenster-Kontakt ID = ");
    Serial.print(ID);
    Serial.print(" und USPG = ");
    Serial.println(USPG);

    Serial.print("bin wach...   ");

    // Lese Fensterkontakt ein
    buttonstate = digitalRead(TUERKONTAKT);

    // soll später zur Unterspannungserkennung verwendet werden; per MQTT übertragen und im Widget für Fenster Unterspannungssymbol setzen
    // lese Analogwert über ADC0/TOUT/A0 ein
    // um beim ESP-12 die Betriebsspannung zu lesen, darf Pin ADC nicht beschaltet werden
    // beim NodeMCU geht der Pin ADC des ESP-12 auf einen Spannungsteiler, der mit GND verbunden ist; der obere Widerstand ist auf ADC0/TOUT/A0 geführt 
    // siehe: https://www.electronicwings.com/nodemcu/nodemcu-adc-with-arduino-ide
    Serial.println();
    Serial.print("ADC-Wert: ");
    spannung = ESP.getVcc();
    Serial.println(spannung);

  
    // gib Status Fensterkontakt und Unterspannungserkennung aus
    if (buttonstate == HIGH) {
      
      // Fenster geschlossen

      // Statusmeldung über serielle Schnittstelle
      Serial.print("Fenster zu   ");
      // und über MQTT
      client.publish(ID, "0");
    
    } else {

      // Fenster auf

      // Statusmeldung über serielle Schnittstelle
      Serial.print("ACHTUNG: Fenster auf   ");
      // und über MQTT
      client.publish(ID, "1");
    
    }

    if (spannung < spannungmin) {
      client.publish(USPG, "1");
    } else {
      client.publish(USPG, "0");
    }

    goto_deepsleep(); 
    
}


Der Quelltext kann unter Downloads heruntergeladen werden.

Gehäuse

Gehäuse Fensterkontakt.jpg

Das Gehäuse habe ich mit Tinkercad erstellt. Die kurze Einführung unter Quellen reicht für die Erstellung einfacher Modelle völlig aus.

Das Gehäuse besteht aus dem eigentlichen Gehäuse, dem Deckel und dem Magnethalter. Die STL-Daten stelle ich unter Downloads zur Verfügung.

Ich selbst habe keinen 3D-Drucker, sondern habe die Teile bei verschiedenen Dienstleistern unter www.treatstock.com bestellt. Die Qualität (Abmessungen, Oberfläche, Farbton) war je nach verwendetem 3D-Drucker und Filament recht unterschiedlich, obwohl als Filament immer PLA weiss angegeben wurde. Letzlich habe mich nach Ende der Entwicklung für meine Kleinserie auf einen Dienstleister festgelegt, der einen Prusa i3 MK3S und immer dasselbe Filament verwendet hat. Die Abmessungen habe ich auf ihn angepaßt.

Integration in ioBroker

ioBroker läuft bei mir auf einem Raspi 4 Model b. Der MQTT Server ist als Adapter in ioBroker installiert.

MQTT BrokerClient Adapter.png

Die Datenpunkte, die die Fensterkontakte liefern, erscheinen in ioBroker als Objekte, mit denen die Widgets in VIS gesteuert werden können.

IoBrokerObjekte.png

Auf der Seite Lüftung/Fenster wurde für jedes Fenster ein Widget erstellt, das anzeigt, welches Fenster offen ist.

IoBrokerFenster.png

Auf der Home-Seite von ioBroker will ich nur wissen, ob ein Fenster offen ist und wenn ja, wieviele Fenster offen sind. Hierfür definiert man eine Aufzählung, die dann von einem Script ausgewertet wird. Damit wird wieder ein Widget ansteuert.

IoBroker Aufzählung.png IoBrokerHome.png

Downloads

Quellen