AdSense

Posts mit dem Label Home Automation werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Home Automation werden angezeigt. Alle Posts anzeigen

Sonntag, 30. August 2020

Home Automation mit Raspberry PI und ESP8266, Teil 2: Ein einfacher Sensor (ESP8266)

(English version) Als ersten Teil möchte ich auf meine Temperatur- und Luftfeuchte-Sensoren eingehen. Ich nutze hierfür einen ESP8266, welcher einen Si7021-Sensor ausliest. Den Si7021 habe ich mit einem 4-poligen Kabel mit dem ESP8266 an den Pins D1-D4 verbunden, wie man es hier im Bild sehen kann:

Zusätzlich muss noch der Pin D0 mit RST verbunden werden, damit das Deep Sleep funktioniert, welches wir noch brauchen werden. Da ich außerdem relativ instabile Handyladegeräte für die Sensoren benutzt habe, habe ich noch einen Kondensator parallel zu G und 3V geschalten. 

Das Ganze kommt dann in ein 3D-gedrucktes Gehäuse, welches man hier auf Thingiverse finden kann: https://www.thingiverse.com/thing:4583759. Mit dem kleinen Bügel wird der Sensor im Gehäuse fixiert, danach kommt der ESP oben drauf und der Deckel fixiert dann alles. An der offenen Stelle ist nun genau genug Platz für den Anschluss des Handyladegeräts.



Der Code ist vergleichbar mit meinem anderen Post zu dem Thema, https://physudo.blogspot.com/2019/07/wlan-thermometer-mit-dem-dht22-und.html. Zuerst wird im Setup-Teil das WLan verbunden, sollte das nicht klappen resettet sich der ESP automatisch nach 100 Sekunden. Außerdem wird der Si7021-Sensor initialisiert.

In Loop wird dann, falls das WLan verbunden ist (ansonsten wird der ESP auch resettet), die Temperatur und Luftfeuchte gemessen. Sollte hier etwas schief gehen wird auch der ESP neu gestartet. Anschließend wird alles per http Request an den Raspberry gesendet, wie im vorigen Post beschrieben (https://physudo.blogspot.com/2020/08/home-automation-mit-raspberry-pi-und.html).

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
const char* ssid     = "WIFI SSID";
const char* password = "WIFI PASSWORD";

#include "Adafruit_Si7021.h"

Adafruit_Si7021 sensor = Adafruit_Si7021();

void setup()
{
  Serial.begin(9600);

  Serial.print("Your are connecting to;");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  int resetCtr = 0;
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    resetCtr ++;
    Serial.print(".");
    if (resetCtr > 200)
    {
      ESP.restart();
    }
  }

  pinMode(D0, WAKEUP_PULLUP);
  pinMode(D3, OUTPUT);
  pinMode(D4, OUTPUT);
  digitalWrite(D3, HIGH);
  digitalWrite(D4, LOW);
  delay(500);

  while (!sensor.begin())
  {
    Serial.println("Did not find Si7021 sensor!");
  }

  digitalWrite(D3, LOW);
}



void loop() {
  if (WiFi.status() != WL_CONNECTED)
  {
    ESP.restart();
  }

  if (WiFi.status() == WL_CONNECTED)
  {
    Serial.println("");
    Serial.println("Your ESP is connected!");
    Serial.println("Your IP address is: ");
    Serial.println(WiFi.localIP());

    digitalWrite(D3, HIGH);
    delay(500);
    float t = sensor.readTemperature();
    Serial.print("Temperature: ");
    Serial.println(t);
    float h = sensor.readHumidity();
    Serial.print("Humidity: ");
    Serial.println(h);
    if (isnan(t))
    {
      t = sensor.readTemperature();
    }
    if (isnan(h))
    {
      h = sensor.readHumidity();
    }
    digitalWrite(D3, LOW);

    if (isnan(t))
    {
      ESP.restart();
    }
    if (isnan(h))
    {
      ESP.restart();
    }
    

        
    HTTPClient http;
    char requestString[255] = "";
    sprintf(requestString, "http://raspberrypi/SendValues.php?name=Temperatursensor_1&split=1&data=%f,%f", t, h);    
    http.begin(requestString);
    int httpCode = http.GET();
    Serial.println(httpCode);

    if (httpCode > 0)
    {
      String payload = http.getString();
      Serial.println(payload);
    }

    http.end();

    ESP.deepSleep(300e6); 
    delay(300000);
    ESP.reset();
  }
  else
  {
    Serial.println("");
    Serial.println("WiFi not connected");
    delay(1000);
  }
}

Home Automation mit Raspberry PI und ESP8266, Teil 1: Die Grundlagen (Raspberry PI)

(English version) Heute möchte ich anfangen, mein Smart Home System zu präsentieren. Ich habe damit Anfang des Jahres angefangen und inzwischen ist es ziemlich groß gewachsen. Aktuell besteht das System aus:

  • 6 Temperatur- und Luftfeuchte-Sensoren innen
  • 3 Temperatur- und Luftfeuchte-Sensoren außen
  • 2 Temperatur- und Luftfeuchte-Sensoren in unseren zwei Terrarien
  • 1 CO2-Sensor
  • 5 schaltbaren Steckdosen
  • 3 schaltbaren Lampen
  • 1 Umschalter für die Eingänge vom Soundsystem
  • Sensoren an Waschmaschine und Trockner
  • Mikrofon und zwei Kameras am 3D-Drucker
  • Dashboard als Website an einem alten Tablet bzw. auf dem Smartphone
  • Telegram-Bot mit automatischen Benachrichtigungen und Kommandos

Die Grundlage für alles ist ein Raspberry PI 4, welcher als Server im WLAN fungiert. Hier werden alle Daten gespeichert, die Website liegt hier und alle Sensoren kommunizieren mit ihm. Auf dem Raspberry PI muss eigentlich nicht viel eingerichtet werden, ich habe den Apache2 Webserver installiert, hier findet man zahllose Tutorials, im Endeffekt ist es aber nur der folgende Befehl:

sudo apt install apache2

Außerdem habe ich den Apache2 für Php installiert, auch dazu gibt es viele Tutorials, der Befehl hierfür wäre:

sudo apt-get install php libapache2-mod-php

Das sind nun eigentlich schon alle Sachen die auf dem Raspberry PI eingerichtet werden müssen. Es empfiehlt sich aber, den Ordner /var/www/html/ als Share freizugeben, dann kann man auch von einem anderen Computer im Netzwerk auf alle Daten und Skripte zugreifen. Außerdem habe ich noch den Ordner /var/www/html/data/ angelegt, hier werden alle gespeicherten Daten abgelegt.

Damit ist schon alles eingerichtet und es kann mit den ersten Skripten losgehen. Hier möchte ich nur das grundlegende Skript zum Senden von Daten posten, alle weitere Modifikationen und Skripte werde ich dann bei den jeweiligen Posts präsentieren. Zum Senden von Daten nutze ich die Datei "SendValues.php", welche über GET-Parameter die folgenden Daten vom Sensor erwartet:

  • "name", also der Name des Sensors, z.B. "Temperatursensor_1"
  • "split", wie die Daten in verschiedenen Dateien aufgesplittet werden. Die Idee ist hier, wenn Sensoren über Jahre hinweg laufen, dass die hier nicht riesige Dateien entstehen, die man dann gar nicht mehr öffnen kann, sondern man z.B. für jeden Monat oder jedes Jahr eine eigene Datei hat. Split 0 bedeutet kein Aufsplitten, 1 bedeutet eine Datei pro Jahr, 2 eine Datei pro Monat und 3 eine Datei pro Tag
  • "data", die eigentlichen Daten, per Komma getrennt. z.B. für einen Temperatur- und Luftfeuchte-Sensor: 24.1,54.6

Das Skript besteht nun aus folgenden Teilen. Zuerst der Header:

<html>
 <head>
  <title>Send Sensor Values</title>
 </head>
 <body>
  <?php 

Danach beginnt der eigentliche Code. Hier werden die Daten gelesen und das aktuelle Datum bestimmt:

$name = htmlspecialchars($_GET["name"]);
$split = htmlspecialchars($_GET["split"]);
$data = htmlspecialchars($_GET["data"]);

$date = gmdate("Y-m-d") . "T" . gmdate("H:i:s.u") . "Z";

Als nächstes wird eine [name]_last.csv-Datei erstellt, welche den aktuellsten Wert hat:

$file = "data/".$name."_last.csv";
$Saved_File = fopen($file, 'w');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);

Als nächstes wird nun das Splitten gemacht. Dazu wird der Dateiname erstellt, [name]_[Jahr]-[Monat]-[Tag].csv. Da ich einen Überlapp zwischen den verschiedenen Dateien haben will erstellt er auch gleich die nächste Datei, also für nächstes Jahr, nächsten Monat, nächsten Tag, und schreibt hier auch den Wert rein, das sorgt dafür, dass wenn ich mir das letzte Jahr plotten möchte, ich nicht nur Werte bis zum 1.1. habe, sondern auch von dem Jahr davor, sodass der Zeitraum von einem Jahr auf jeden Fall in der Datei enthalten ist. $file1 ist die aktelle Datei, $file2 die für den nächsten Zeitraum.

$file = "data/".$name.".csv";

if ($split == "1")
{
  $file = "data/".$name."_" . gmdate("Y") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y", strtotime("+1 year")) . ".csv";
}
if ($split == "2")
{
  $file = "data/".$name."_" . gmdate("Y-m") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y-m", strtotime("+1 month")) . ".csv";
}
if ($split == "3")
{
  $file = "data/".$name."_" . gmdate("Y-m-d") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y-m-d", strtotime("+1 day")) . ".csv";
}

Anschließend wird das nun in die Dateien gespeichert. Falls "split" 0 ist, wird auf das Speichern in die zweite Datei verzichtet, da es ja für den gesamten Zeitraum nur eine Datei gibt.

$Saved_File = fopen($file, 'a');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);
if ($split == "0")
{
}
else
{
  $Saved_File = fopen($file2, 'a');
  fwrite($Saved_File, $date . "," . $data . "\r\n");
  fclose($Saved_File);
}

Und das war auch schon der Code, man muss jetzt noch die ganzen Tags wieder zu machen, und schon ist SendValues.php fertig:

  ?>
 </body>
</html>

Das ist nun eigentlich schon alles, was man auf der Server-Seite benötigt, als nächstes können die Sensoren ihre Werte senden. Zum Ausprobieren kann man das auch über den Browser im Heimnetz testen, dabei sollten die Dateien Temperatursensor_1_last.csv, und noch zwei weitere Dateien mit dem aktuellen und dem kommenden Jahr als Endung erstellt werden:

http://raspberrypi/SendValues.php?name=Temperatursensor_1&split=1&data=25.3,64.59

Für alle, die sich das nicht einzeln kopieren möchten hier nochmal der gesamte Quellcode:

<html>
 <head>
  <title>Send Sensor Values</title>
 </head>
 <body>
  <?php
 
$name = htmlspecialchars($_GET["name"]);
$split = htmlspecialchars($_GET["split"]);
$data = htmlspecialchars($_GET["data"]);

$date = gmdate("Y-m-d") . "T" . gmdate("H:i:s.u") . "Z";

$file = "data/".$name."_last.csv";
$Saved_File = fopen($file, 'w');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);

$file = "data/".$name.".csv";

if ($split == "1")
{
  $file = "data/".$name."_" . gmdate("Y") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y", strtotime("+1 year")) . ".csv";
}
if ($split == "2")
{
  $file = "data/".$name."_" . gmdate("Y-m") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y-m", strtotime("+1 month")) . ".csv";
}
if ($split == "3")
{
  $file = "data/".$name."_" . gmdate("Y-m-d") . ".csv";
  $file2 = "data/".$name."_" . gmdate("Y-m-d", strtotime("+1 day")) . ".csv";
}
$Saved_File = fopen($file, 'a');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);
if ($split == "0")
{
}
else
{
  $Saved_File = fopen($file2, 'a');
  fwrite($Saved_File, $date . "," . $data . "\r\n");
  fclose($Saved_File);
}

  ?>
 </body>
</html>

Dienstag, 11. August 2020

Machine Learning für einen Kamera-überwachten 3D-Drucker

(English version) Nach über einem Jahr seit meinem letzten Post möchte ich heute endlich wieder eines meiner Projekte vorstellen. Im letzten Jahr habe ich an einigen Sachen gearbeitet, vor allem dem Upgrade meines Ultimakers 2+ auf 5 verschiedene Farben. Der Post heute tangiert das Thema, ist aber gleichzeitig auch teil meines IoT-Systems: Eine kamerabasierte Überwachung für einen 3D-Drucker mit automatischer Zustandserkennung.

Zuerst zu den Kameras. Ich habe zwei EPS32-Cam module, wie diese programmiert werden ist bereits im Internet ausführlich erklärt. Diese habe ich an meinem 3D-Drucker angebracht, die erste Kamera von oben, mit einem 3D-gedruckten gestell welches man hier herunterladen kann: https://www.thingiverse.com/thing:3899159

Die zweite Kamera is im 3D-Drucker mit Klebeband fixiert und hat unter dem Kameramodul noch einen kleinen 45°-Block um im richtigen Winkel zu stehen.

Die Kameras erzeugen nun etwa alle 3 Minuten ein neues Bild, insofern hat sich inzwischen schon eine gute Menge an Trainingsdaten angesammelt. Die beiden Blickwinkel sehen so aus:



Die Bilder können in verschiedene Kategorien klassifiziert werden:

  1. Leer: Der Druckkopf ist an seiner normalen Position und das Druckbett ist leer
  2. Vorbereiten: Der Druckkopf ist vorne links und der Drucker heizt sich auf
  3. Drucken: Das versteht sich von selbst
  4. Fertig: Der Druckkopf ist an seiner normalen Position und das Druckbett ist nicht leer
  5. Problem: Ein Abstand zwischen Druckbett bzw. Druckobjekt und Düse ist erkennbar
  6. Wartung: Meine Hände sind sichtbar, der Druckkopf ist zerlegt oder nicht an seiner normalen Position
  7. Aus: Alle Lichter sind aus und die Bilder sind schwarz

Kamera 1 kann den Unterschied zwischen Drucken und Problem nicht erkennen, also erkennt diese Kamera die Kategorie "Problem" nicht, Kamera 2 kann nicht zwischen Leer und Fertig unterscheiden, also kann diese Kamera die Kategorie "Fertig" nicht erkennen.

Ich habe die ersten Bilder die ich hatte dann in die 7 Kategorien sortiert und dann ein neuronales Netz darauf trainiert. Meine Architektur des Netzes (in Tensorflow, Python) sieht folgendermaßen aus:

model = models.Sequential()
model.add(layers.Conv2D(32, (5, 5), activation='relu', input_shape=(128, 128, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(7))

Das Netz ist für einen Input mit 128x128 Pixeln mit jeweils 3 Farbkanälen ausgelegt und besitzt 5 convolutional Layers, welche jeweils ein max Pooling nachgeschaltet haben. Danach gibt es noch ein fully connected Layer mit 64 Neuronen welches dann zum output Layer führt, welches 7 neuronen hat.

Um die Erkennung weiter zu verbessern habe ich ein zweites Netz erstellt, welches Bilder der Größe 64x64 nutzt und daher nur 4 convolutional Layers hat. Die Ergebnisse der Netze in Prozent werden dann addiert (und durch 2 gteilt), dies verbessert die Genauigkeit weiter. Am Ende wird nun das Ergebnis beider Kameras addiert, zusätzlich dazu habe ich noch ein Mikrofon im Drucker, welches über eine Messung des Geräuschlevels erkennt, ob der Lüfter im Druckkopf an ist, sodass ich hier noch einen weiteren Input habe. Das Gesamtergebnis aus beiden Kameras und dem Mikrofon liefert mir dann eine Vorhersage für den Status des Druckers. Wie im vorherigen Post erklärt wird dieses Ergebnis dann an mein IoT-System gesendet, welches mir eine Nachricht schickt, falls

  • Der Drucker seinen Status zu "Problem" ändert
  • Der Drucker seinen Status zu "Fertig" ändert

Dienstag, 2. Juli 2019

WLAN-Thermometer mit dem DHT22 und EPS8266-12E

(English version) Eigentlich sollte ich ja an den Posts zum Drucker weitermachen, aber ich habe gerade an einem ESP8266-12E gearbeitet und dabei super einfach ein WLAN-Thermometer (mit dem ESP8266-12E als Web-Client, welcher an einen Apache-Server auf meinem Raspberry PI sendet) realisiert, sodass ich das erstmal hier erklären möchte.


1. Der ESP8266-12E
Der ESP8266-12E ist ein Arduino-ähnlicher Baustein, welcher einen WLAN-Baustein mit integriert hat. Sucht man auf Google danach, findet man diesen bei vielen Quellen zu kaufen. Das Tolle ist, dass er mit der Arduino-Umgebung programmiert werden kann, sodass alle Libraries auch funktionieren!

2. Einrichtung Arduino für den ESP8266-12E
Zuerst sollte der CH340-Treiber installiert werden. Da es dazu genug im Internet gibt werde ich darauf nicht eingehen. Als nächstes muss in der Arduino-Umgebung auf die Voreinstellungen gehen:

Hier trägt man nun die zusätzliche Boardverwalter-URL ein:
http://arduino.esp8266.com/stable/package_esp8266com_index.json

Danach kann man dann auch schon im Boardverwalter die Einstellungen für die Boards herunterladen:

Hier muss nun nach "ESP8266" gesucht werden und das sollte dann auch installiert werden:

Nun ist eigentlich alles vorbereitet und wir können das Board auswählen, ich habe dabei "NodeMCU 1.2 (ESP8266-12E Module)" ausgewählt:


3. Der Code
Zu dem Code gibt es eigentlich nicht viel zu erklären. Einerseits muss man seine WLAN-Daten eingeben, darunter wird der DHT22-Sensor initialisiert. In "Loop" wird getestet ob man mit dem WLAN verbunden ist, sollte dies der Fall sein, so wird Temperatur und Luftfeuchte gemessen (float t und float h), diese Werte werden in einen String (requestString) geschrieben und dann als HTTP Get-Request an einen Webserver gesendet. Den Code für den Web-Server habe ich unten angefügt.

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
const char* ssid     = "***";
const char* password = "***";   
int wifiStatus;
    
#include "DHT.h"
#define DHTPIN D2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);
  // put your setup code here, to run once:

  Serial.print("Your are connecting to;");
  Serial.println(ssid);
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  dht.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  wifiStatus = WiFi.status();
  
  if(wifiStatus == WL_CONNECTED){
    Serial.println("");
    Serial.println("Your ESP is connected!");  
    Serial.println("Your IP address is: ");
    Serial.println(WiFi.localIP()); 
    float t = dht.readTemperature(); 
    float h = dht.readHumidity(); 
    HTTPClient http;
    char requestString[255] = "";
    sprintf(requestString, "http://webServer.de/Send.php?name=TemperatureSensor&data=%f,%f", t, h);
    http.begin(requestString);  //Specify request destination
    int httpCode = http.GET();                                                                  //Send the request
    
    if (httpCode > 0) 
    { //Check the returning code
      String payload = http.getString();   //Get the request response payload
      Serial.println(payload);                     //Print the response payload
    }
    
    http.end();   //Close connection
    
    delay(300000);
  }
  else{
    Serial.println("");
    Serial.println("WiFi not connected");
    delay(1000);
  }
}

4. Der Server-Code
Auf meinem Raspberry Pi läuft ein Apache-Webserver. In /var/www liegt die Datei "Send.php", welche einfach Sensordaten annimmt und in .csv-Dateien schreibt. Dabei übergibt der Sensor im Get-Befehl seinen Namen, das wird dann auch der Dateiname, hier wird dann mit aktuellem Zeitstempel der Inhalt von "data" angehängt, was in diesem Fall die Temperatur in °C und die Luftfeuchte in % ist.

<html>
 <head>
  <title>Send Sensor Values</title>
 </head>
 <body>
  <?php

$name = htmlspecialchars($_GET["name"]);
$data = htmlspecialchars($_GET["data"]);

$date = date("Y-m-d") . "T" . date("H:i:s.u") . "Z";

$file = "data/".$name.".csv";
$Saved_File = fopen($file, 'a');
fwrite($Saved_File, $date . "," . $data . "\r\n");
fclose($Saved_File);

  ?>
 </body>
</html>

Sonntag, 11. September 2016

DHT11 Sensor mit Arduino auslesen

(English version) Vor einizger Zeit habe ich einen Temperatur- und Luftfeuchte-Sensor gekauft, die Bezeichnung lautet DHT11. Der Sensor ist einfach zu benutzen und verträgt sowohl 3,3V als auch 5V. Der Sensor hat 4 Pins, für den normalen Betrieb werden aber nur 3 Pins benötigt.
Der linke Pin (wie im Bild erkennbar) wird mit 5V verbunden, der rechte Pin mit GND. Der linke der beiden mittleren Pins ist der Signal-Pin, dieser wird mit einen beliebigen Digital-Pin am Arduino verbunden (in diesem Fall ist das Kanal 2). Außerdem wird noch ein 10k Widerstand zwischen dem Daten-Pin und 5V benötigt.

Als nächstes benötigt man die entsprechende Library, diese gibt es hier: Download. In der Arduino Software wird dann der Sensor wie folgt ausgelesen. Der Quellcode sollte selbsterklärend sein. Falls ein Sensor mit höherer Genauigkeit benötigt wird kann auch ein DHT22 benutzt werden, das muss dann entsprechend in DHTTYPE geändert werden.

#include "DHT.h"
#define DHTPIN 2

#define DHTTYPE DHT11

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(9600);

  dht.begin();
}

void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  
  Serial.print("Humidity: ");
  Serial.print(h);
  Serial.print(" %\t");
  Serial.print("Temperature: ");
  Serial.print(t);
  Serial.println(" *C ");

  delay(2000);
}

Montag, 9. Juni 2014

Helligkeitssensor BH1750FVI

(English version) Für mein "Home Automation"-Projekt habe ich auch eine Automatisierung des Rollladens geplant. Dabei soll der Rollladen geschlossen werden, wenn die Sonne ins Zimmer scheint um so ein Aufheizen des Zimmers zu verhindern. Um Sonneneinstrahlung zu erkennen verfolge ich im Moment zwei Ansätze: Erkennung über Helligkeit und Temperatur.

Zur Erkennung der Sonneneinstrahlung über die Helligkeit setze ich einen Helligkeitssensor vom Typ BH1750FVI ein. Dieser ist (wie so oft) für unter 2 Euro bei Ebay erhältlich.

Der Sensor misst die Helligkeit zwischen 0 und 65535 lx und schickt das Ergebnis der Messung über den I2C-Bus an den Mikrocontroller. Die Messung der Helligkeit kann kontinuierlich oder einmalig mit einer Auflösung von 0.5, 1 oder 4 lx erfolgen.

Die Ansteuerung des Sensors bzw. die Abfrage der Helligkeit über I2C funktioniert "von Hand" schon ziemlich einfach, noch komfortabler wird es jedoch mit dem Arduino unter Verwendung der passenden Library. Ich habe die Lib von Christopher Laws verwendet.

Der Sensor wird einfach über VCC und GND mit 5V verbunden, SDA und SCL werden an die entsprechenden Pins des Arduino, ATmega oder ATtiny angeschlossen.

Dann kann der Beispiel-Code der Library übertragen werden:
#include <Wire.h>
#include <BH1750.h>

BH1750 lightMeter;

void setup(){
  Serial.begin(9600);
  lightMeter.begin();
  Serial.println("Running...");
}


void loop() {
  uint16_t lux = lightMeter.readLightLevel();
  Serial.print("Light: ");
  Serial.print(lux);
  Serial.println(" lx");
  delay(1000);
} 
 
Der Code ist weitestgehend selbsterklärend. Im Setup wird der Sensor mit der begin()-Funktion initialisiert. Wenn gewünscht kann hier durch Übergabe eines entsprechenden Parameters der Mess-Modus (einmalig/kontinuierlich bzw. die Auflösung) eingestellt werden. Näheres dazu ist dem h-File der Library zu entnehmen. Der aktuelle Helligkeitswert in Lux kann dann über die Funktion readLightLevel() ausgelesen werden. Und das war es auch schon, mehr gibts dazu nicht zu sagen ;)

Sonntag, 16. Februar 2014

"Home Automation" mit dem Arduino und 433 MHz - Die komplette Fernsteuerung

(English version) Seit meinem letzten Beitrag zu diesem Thema sind ja nun einige Monate vergangen. Aber nun habe ich es endlich mal geschafft, die Steuerung für die Steckdose und den Rolladen in ein Programm zu bringen und vorallem das ganze vom Steckbrett auf eine Platine zu übertragen.

Hardware

Die Hardware ist recht unspektakulär:

Im Prinzip besteht die Fernbedinung nur aus einem Attiny44A, einem Pullup-Widerstand für den Reset-Eingang, drei Knöpfen (hoch, runter, stopp), einem Halter für eine CR3032-Knopfzelle und natürlich dem 433MHz-Sender. Die drei Knöpfe sind jeweils mit einem Eingang des Attiny verbunden und ziehen den Eingang bei Betätigung auf Ground

Software

Die einzelnen Funktionen der Software wurden bereits in den vorhergehenden Posts zum Thema erläutert. Außerdem ist der Code (für meine Verhältnisse) gut kommentiert. Ich werde daher nicht auf den kompletten Code eingehen.

#include <RCSwitch.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

RCSwitch mySwitch = RCSwitch();
unsigned char buttonDown = 10;
unsigned char buttonStopp = 9;
unsigned char buttonUp = 7;
unsigned char buttonPressed = 0;

char stopRequest = 0;

void setup() {
  //Interrupts global deaktivieren
  cli();

  //Pins initialisieren
  pinMode(buttonDown, INPUT_PULLUP);
  pinMode(buttonStopp, INPUT_PULLUP);
  pinMode(buttonUp, INPUT_PULLUP);

  //Funkmodul intialisieren und Steckdose ausschalten
  mySwitch.enableTransmit(0);  //Sender hängt an Pin 0
  mySwitch.setProtocol(1);
  mySwitch.switchOff("11011", "10000");

  //Energie sparen
  ADCSRA &= ~(1<<ADEN); //Deaktiviere ADC
  ACSR = (1<<ACD); //Deaktiviere Analog Comparator

  //Pin-Change-Interrupt intialisieren
  PCMSK1 |= (1<<PCINT8); //Pin-Change-Interrupt an Pin 2 (Arduino Pin  10)
  PCMSK1 |= (1<<PCINT9); //Pin-Change-Interrupt an Pin 3 (Arduino Pin 9)
  PCMSK0 |= (1<<PCINT7); //Pin-Change-Interrupt an Pin 6 (Arduino Pin 7)
 
  //Power-Down-Modus vorbereiten
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

  //Interrupts global aktivieren
  sei();
}
void loop() {
  sendCommand();
  powerDown();
} 

void sendCommand() {
  if (buttonPressed == buttonDown) {
    mySwitch.switchOn("11011", "10000");
    delay(750);
    sendCommandDown();
    powerDown(25);
    mySwitch.switchOff("11011", "10000");
    buttonPressed = 0;
  }

  else if (buttonPressed == buttonUp) {
    mySwitch.switchOn("11011", "10000");
    delay(750);
    sendCommandUp();
    powerDown(25);
    mySwitch.switchOff("11011", "10000");
    buttonPressed = 0;
  }
}

//Befehl Rolladen rauf
void sendCommandUp() {
  mySwitch.setProtocol(4);
  mySwitch.sendQuadState("0F0F0100QQ0F100F0F0F");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0F1Q");
  mySwitch.setProtocol(1);
}

//Befehl Rolladen stopp
void sendCommandStopp() {
  mySwitch.setProtocol(4);
  mySwitch.sendQuadState("0F0F0100QQ0F100FFFFF");
  mySwitch.setProtocol(1);
}

//Befehl Rolladen runter
void sendCommandDown() {
  mySwitch.setProtocol(4);
  mySwitch.sendQuadState("0F0F0100QQ0F100F0101");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0110");
  mySwitch.setProtocol(1);
}

//lässt Attiny für time Sekunden warten
void powerDown(char time) {
  GIMSK |= (1<<PCIE1); //Pin-Change-Interrupt aktivieren
  GIMSK |= (1<<PCIE0);
  
  stopRequest = 0;

  for (char i = 1; i<= time*1000; i++) {
    if (stopRequest == 0) {
      delay(1);
    }
  }

  GIMSK &= ~(1<<PCIE1); //Pin-Change-Interrupt deaktivieren
  GIMSK &= ~(1<<PCIE0);
}

//setzt Attiny solange in Powerdown-Modus, bis Pin-Change-Interrupt ausgelöst wurde
void powerDown() {
  GIMSK |= (1<<PCIE1); //Pin-Change-Interrupt aktivieren
  GIMSK |= (1<<PCIE0);
  sleep_mode();  //Schlafen gehen
  //hier gehts weiter nach aufwachen
  GIMSK &= ~(1<<PCIE1); //Pin-Change-Interrupt deaktivieren
  GIMSK &= ~(1<<PCIE0);
}

void checkButton() {
  if (digitalRead(buttonDown) == LOW) {
    buttonPressed = buttonDown;
  }

  else if (digitalRead(buttonStopp) == LOW) {
    stopRequest = 1;
    mySwitch.switchOff("11011", "10000");
  }

  else if (digitalRead(buttonUp) == LOW) {
    buttonPressed = buttonUp;
  }
}

//ISR für PCINT1 und PCINT0 (Pin-Change-Interrupts)
ISR(PCINT1_vect)
{
  checkButton();
} 
ISR(PCINT0_vect)
{
  checkButton();
} 

Einige Details der Software möchte ich dennoch erläutern. Die Spannungsversorgung der Fernbedienung erfolgt über eine CR2032-Knopfzelle. Da diese so lange wie möglich halten soll und die Fernbedienung 99,999% der Zeit sowieso nur rum liegt und nichts tut, geht der Attiny bei Inaktivität mit der Funktion powerdown() in den Schlaf-Modus. Er schläft so lange, bis er durch einen Tastendruck (Pin Change Interrupt) wieder geweckt wird. Um im Schlaf-Modus so wenig Energie wie möglich zu verbrauchen, wird zu Beginn zusätzlich der AD-Wandler sowie der Analog Comparator abgeschaltet. Dadurch sinkt der Ruhestrom auf wenige µA.

Sonntag, 18. August 2013

"Home Automation" mit dem Arduino und 433 MHz - Der Rolladen, Teil 2

(English version) Die Theorie zur Steuerung des Rolladens ist bekannt - jetzt geht es an die praktische Ansteuerung.

Das Protokoll zur Steuerung des Rolladens ist ja wie bereits erwähnt sehr ähnlich dem Protokoll zur Steuerung der Funksteckdosen. Da lag es nahe, bei der Entwicklung der passenden Software nicht ganz bei Null anzufangen, sondern die bereits existierende und bewährte rc-switch-Library zu verwenden. Natürlich sind einige Anpassungen nötig.

Anpassungen rc-switch

Die Library bietet von Haus aus drei Protokolle für unterschiedliche Steckdosen. Diese unterscheiden sich hauptsächlich in der Pulslänge und der Form des Sync-Bits. Ich habe mich entschieden, für den Rolladen ein viertes Protokoll hinzuzufügen.

Die erste Anpassung muss also in der Funktion setProtocol() vorgenommen werden:

void RCSwitch::setProtocol(int nProtocol) {
  this->nProtocol = nProtocol;
  if (nProtocol == 1){
    this->setPulseLength(350);
  }
  else if (nProtocol == 2) {
    this->setPulseLength(650);
  }
  else if (nProtocol == 3) {
    this->setPulseLength(100);
  }
  //edit: Protokoll 4 für Rolladen mit Pulslänge 250ms und 4 Wiederholungen
  else if (nProtocol == 4) {
    this->setPulseLength(250);
    this->setRepeatTransmit(4);
  }
}

Alle Änderungen sind mit einem //edit-Kommentar markiert. Ich habe hier im Prinzip nur einen vierten else-if-Zweig für das Protokoll 4 eingefügt. Wird das Protokoll 4 gewählt, setzt der Code die Pulslänge (ein Puls entspricht einem Viertel) auf 250µs.Außerdem wird die Funktion setRepeatTransmit mit dem Parameter 4 aufgerufen. Dies bewirkt, dass jeder Befehl automatisch immer viermal gesendet wird (so wie es auch die Fernbedienung macht).

Als nächste Anpassung musste eine Funktion eingefügt werden, mit der das Senden eines Quad-State-Befehls möglich ist. Dafür habe ich einfach die sendTriState()-Funktion erweitert:

//edit: Quad-State-Befehl mit zusätzl. Q senden
void RCSwitch::sendQuadState(char* sCodeWord) {
  for (int nRepeat=0; nRepeat<nRepeatTransmit; nRepeat++) {
    //Syn-Bit wird hier VOR Übertragung gesendet!
 this->sendSync(); 
 int i = 0;
    while (sCodeWord[i] != '\0') {
      switch(sCodeWord[i]) {
        case '0':
          this->sendT0();
        break;
        case 'F':
          this->sendTF();
        break;
        case '1':
          this->sendT1();
        break;
        case 'Q':
          this->sendQQ();
        break;
      }
      i++;
    }   
  }
}

Ein großer Unterschied zwischen Rolladen und Steckdosen ist das Sync-Bit. Während es bei den Steckdosen erst NACH Übertragung des Befehls gesendet wird, muss beim Rolladen zuerst das Sync-Bit und dann der Befehl übertragen werden. Daher wird in der Funktion sendQuadState die Funktion sendSync() zu Beginn der Funktion aufgerufen - bei der ursprünglichen sendTriState()-Funktion erfolgte der Aufruf erst am Ende. Als weitere Änderung habe ich einen vierten Case eingefügt. Stößt das Programm beim "abarbeiten" des zu sendenden Codeworts auf ein Q, wird die (ebenfalls von mir eingefügt) Funktion sendQQ() aufgerufen.

/**
 * edit: Sends a Quad-State "Q" Bit
 *            ___   _
 * Waveform: |   |_| |___
 */
void RCSwitch::sendQQ() {
  this->transmit(3,1);
  this->transmit(1,3);
}

Die Funktion sendet mit transmit(3,1) zuerst ein langes HIGH-Signal, gefolgt von einem kurzen HIGH-Signal. Der erste Parameter der Funktion gibt an, wie viele Pulslängen die HIGH-Phase andauert. Der zweite Parameter gibt entsprechend die Länge der LOW-Phase an.

Die letzte Anpassung wurde in der Funktion zum Senden des Sync-Bits vorgenommen:

void RCSwitch::sendSync() {

    if (this->nProtocol == 1){
        this->transmit(1,31);
    }
    else if (this->nProtocol == 2) {
        this->transmit(1,10);
    }
    else if (this->nProtocol == 3) {
        this->transmit(1,71);
    }
 
    //edit: Syn-Bit für Rolladen
    else if (this->nProtocol == 4) {
        this->transmit(18,6);
    }
}

Das Sync-Bit des Rolladens besteht bekanntermaßen aus einer HIGH-Phase von 4500µs Dauer, gefolgt von einer 1500µs andauernden LOW-Phase. Das Sync-Bit besteht also aus 18 Pulslängen HIGH und 6 Pulslängen LOW (bei einer Pulslänge von 250µs).

Mit diesen kleinen Änderungen lässt sich die rc-switch Library nun auch zur Steuerung des Rolladens verwenden.

Jetzt wird es spannend

Nach so viel Theorie soll nun endlich der Praxistest erfolgen!

Also schnell ein kleines Programm zusammen gebastelt, dass unter Verwendung der modifizierten Library den Rolladen hoch und runter fahren lässt:

#include <RCSwitch.h>

RCSwitch mySwitch = RCSwitch();

void setup() {
  Serial.begin(9600);
  Serial.println("Bereit...");
  mySwitch.enableTransmit(10);
  mySwitch.setProtocol(4);
}

void loop() {
  //runter
  Serial.println("Runter...");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0101");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0110");
  
  delay(5000);
  
  //Stopp
  Serial.println("Stopp...");
  mySwitch.sendQuadState("0F0F0100QQ0F100FFFFF");
  
  delay(1000);
  
  //rauf
  Serial.println("Rauf...");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0F0F");
  mySwitch.sendQuadState("0F0F0100QQ0F100F0F1Q");
  
  delay(5000);
  
  //Stopp
  Serial.println("Stopp...");
  mySwitch.sendQuadState("0F0F0100QQ0F100FFFFF");
  
  delay(1000);
}

In der setup()-Funktion wird das Protkoll auf 4 gesetzt - sonst kann der Rolladen nichts mit dem Funksignal anfangen. In der loop()-Funktion werden dann mit der neuen sendQuadState()-Funktion zuerst die Befehle zum herunterlassen des Rolladens gesendet (woher die Befehle kommen habe ich in Teil 1 beschrieben). Dann wird 5 Sekunden lange gewartet, der Rolladen wird gestoppt, wieder herauf gefahren, nach 5 Sekunden wieder gestoppt usw.

Im letzten Schritt muss nun nur noch die Steuerung von Steckdose und Rolladen zusammengefasst werden, um die gewünschte Funktionalität (Steckdose an, Rolladen verfahren, Steckdose aus) zu erhalten.

Samstag, 17. August 2013

"Home Automation" mit dem Arduino und 433 MHz - Der Rolladen, Teil 1

(Englisch version) Seit meinem letzten Beitrag können wir bereits erfolgreich mit der Funksteckdose kommunizieren. Das Ein- und wieder Ausschalten des Stroms für den Rolladen ist also schonmal kein Problem. Der spannende Teil ist die Kommunikation mit dem Rolladen selbst. Bekannt ist nur, dass sie (wie bei der Steckdose) auf dem 433MHz-Band abläuft. Zum verwendeten Protokoll waren aber keine Infos aufzutreiben. Als erstes habe ich einfach mal die "ReceiveDemo" der rc-switch-Library compiliert, auf den Arduino übertragen und damit versucht, die Signale der Fernbedienung des Rolladens zu lesen. Wie zu erwarten war, hatte ich hier natürlich kein Glück. Die Demo kann nur die üblichen Prokolle der Funksteckdosen auslesen und konnte daher mit der Fernbedienung des Rolladens nichts anfangen.

Reverse Engineering

Um überhaupt einen groben Überblick über das verwendete Protokoll zu erhalten, müssten die Signale der Fernbedienung ausgelesen werden. Dies wäre am einfachsten zu erledigen mit einem Logic Analyzer. Leider habe ich solches Geräte nicht daheim herumfahren - das gibt das Budget nicht her ;)

Also ein bisschen Google bemüht und eine Möglichkeit gefunden, wie man den Arduino als einfachen Logic Analyzer benutzen kann. Wie genau das geht werde ich in einem seperaten Beitrag beschreiben.

Also habe ich einfach den Daten-Pin des Empfangsmoduls mit dem Logic-Analyzer-Arduino verbunden und konnte damit direkt das Signal der Fernbedienung auslesen.

Ein Druck auf den "Hoch"-Knopf lieferte folgendes Signal:

Runter ergab folgendes Signal:

Und Stopp:

Die Übertragung wird immer mit einer Art Sync-Bit gestartet. Dazu sendet die Fernbedienung erst 4500µs ein HIGH-Signal und dann für 1500µs ein LOW-Signal. Danach folgt die Datenübertragung. Das Muster ist ähnlich wie das Tri-State-System bei den Steckdosen - aber eben nur ähnlich. Neben den dort bekannten Zuständen 0, 1 und F ist hier noch ein vierter Zustand zu erkennen:
 _ _ _       _
|         | _ |   | _
Es handelt sich also im Prinzip um ein "Quad-State-System". Dementsprechend habe ich den vierten Zustand "Q" genannt. Ein "kurzes" Signal hat die Dauer von 250µs, ein "langes" dauert dementsprechend 750µs.

Theoretisch könnte man das Signal auch rein binär auswerten und ein kurzes HIGH-Signal als "0" und ein langes HIGH-Signal als "1" deuten. Dies wäre jedoch (meiner Meinung nach) unübersichtlich, da man mit ewig langen Ketten aus 0 und 1 hantieren müsste.

Die Signale können nun also nach dem Quad-State-System ausgewertet werden. Es ergeben sich dabei folgende übertragene Code-Wörter:
Hoch =   0F0F0100QQ0F100F 0F0F
Runter = 0F0F0100QQ0F100F 0101
Stopp =  0F0F0100QQ0F100F FFFF

Es fällt sofort auf, dass die ersten 16 Bits aller Codewörter identisch sind, lediglich die letzten 4 Bits unterscheiden sich. Es liegt also die Vermutung nahe, dass es sich bei den ersten 16 Bits um die Adresse des Rolladens handelt und bei den restlichen Bits um den Befehl.

Fernbedienung auslesen

Um das Signal der Fernbedienung einfach auslesen zu können, habe ich zunächst ein Programm geschrieben, das auf Signale wartet und diese dann im Quad-State-Format ausgibt.

int logfile[40];
int i = 0;
float lastTime = 0;
boolean capturing = false;
boolean checking = false;
boolean dataIncoming = false;

void setup() {
  Serial.begin(9600);
  Serial.println("Bereit...");
  pinMode(2, INPUT);
  attachInterrupt(0, handleInterrupt, CHANGE);
}

void loop() {
}

Zuerst werden diverse Variablen deklariert - unter anderem ein "logfile", in dem später der ausgelesene Code gespeichert wird. Die setup()-Funktion ist sehr übersichtlich, hier wird nur die serielle Verbindung gestartet sowie der Interrupt auf Pin 2 aktiviert (dort hängt der Daten-Pin des Empfangsmoduls). Der Interrupt wird jedes Mal aktiviert, wenn sich das Signal am Pin ändert.
Noch übersichtlicher ist die loop()-Funktion - sie ist komplett leer ;)

Die eigentliche "Arbeit" erfolgt in der Interrupt-Funktion:

void handleInterrupt() {

  if (!capturing) {  //wenn keine Aufnahme läuft
    if (!checking) {  //wenn nicht gerade auf "Start-Signal" geprüft wird
      if (digitalRead(2) == HIGH) {  //wenn Wechsel von LOW nach (jetzt) HIGH
        lastTime = micros();
        checking = true;
      }
    }

    else {    //wenn gerade auf Start-Signal geprüft wird
      if ((micros() - lastTime > 4000) && (digitalRead(2) == LOW)) {    //wenn HIGH-Phase länger als 4ms war und wir jetzt LOW sind
        //das war das Start-Signal
        checking = false;
        capturing = true;
        lastTime = micros();
      }

      else {
        //das war nicht das Start-Signal
        checking = false;
      }
    }
  }

  else {  //es läuft eine Aufnahme
    if (!dataIncoming) {  //bisher noch keine Nutzdaten empfangen
      if ((micros() - lastTime > 1000) && digitalRead(2) == HIGH) {  //das war die lange LOW-Phase vor Beginn der Übertragung
        dataIncoming = true; //ab jetzt kommen Daten  
        lastTime = micros();
      }
    }

    else {  //jetzt wird es interessant, jetzt kommen die Daten
      //wenn steigene Flanke (also jetzt HIGH)
      if (digitalRead(2) == HIGH) {
        //Beginn der HIGH-Phase merken
        lastTime = micros();
      }  

      //wenn fallende Flanke (also jetzt LOW) 
      else if (digitalRead(2) == LOW) {
        //=> prüfe wie lange HIGH war
        if (micros() - lastTime > 500) {
          //long
          logfile[i] = 1;
        }

        else {
          //short
          logfile[i] = 0;
        }

        if (i < 39) {
          //solange noch nicht alle Bits empfangen wurden
          i++;
        }

        else {
          //wir sind fertig
          noInterrupts();  //Interrupts aus damit Ausgabe nicht gestört wird
          Serial.println("Empfangene Daten:");
          //Ausgabe als "quad-bit"
          for (i = 0; i <= 38; i = i + 2) {
            if ((logfile[i] == 0) && (logfile[i+1] == 0))
              Serial.print("0");

            else if ((logfile[i] == 0) && (logfile[i+1] == 1))
              Serial.print("F");

            else if ((logfile[i] == 1) && (logfile[i+1] == 0))
              Serial.print("Q");

            else if ((logfile[i] == 1) && (logfile[i+1] == 1))
              Serial.print("1");
          }
          Serial.println();
          i = 0;
          dataIncoming = false;
          capturing = false;
          interrupts();  //Interrupts wieder an
          return;  //und alles auf Anfang
        }
      }

    }
  }
}

Sie besteht aus mehreren if-else-Abfragen, die den Zustand der diversen Bool-Variablen überprüfen. Als erstes wird überprüft, ob gerade eine "Aufnahme" läuft (capturing). Falls dies nicht der Fall ist, wird untersucht, ob gerade auf das Start-Signal (Sync-Bit) geprüft wird (checking). Falls dies auch nicht der Fall ist (und gerade ein Wechsel von LOW nach HIGH statt gefunden hat) merkt sich der Arduino die aktuelle Zeit und setzt "checking" auf true. Beim nächsten Flankenwechsel läuft das Programm also in den else-Zweig (es wird auf Start-Signal geprüft). Ist der Wechsel auf HIGH länger als 4000µs her kann man davon ausgehen, dass man soeben die lange HIGH-Phase des Sync-Bits empfangen hat. Also merkt sich der Arduino die Zeit, setzt checking auf false und capturing auf true. Dauerte die HIGH-Phase nicht lange genug, wird checking wieder auf false gesetzt, sonst geschieht nichts.

Wurde checking auf true gesetzt, läuft das Programm beim nächsten Flankenwechsel in den entsprechenden else-Zweig. In diesem Zweig wird als erstes geprüft, ob bereits Nutzdaten empfangen werden oder ob das Ende des Sync-Bits erwartet wird (dataIncoming). Das Auslesen der Nutzdaten verläuft relativ simpel. Bei steigender Flanke wird die aktuelle Zeit gespeichert. Bei fallender Flanke wie geprüft, ob die HIGH-Phase länger als 500µs (und somit lang) oder kürzer (und somit kurz) war. Dementsprechend wird im logfile eine 1 oder eine 0 gespeichert - intern arbeitet das Programm als mit dem "doofen" unübersichtlichen Binär-System ;)
Das Ganze wird insgesamt 40mal durchgeführt, dann wurde das komplette Signal empfangen.  Nun wird das empfangene Signal über die serielle Schnittstelle im Quad-State-Format ausgegeben und alle Bedingungen werden wieder auf ihren Ursprungszustand gesetzt. Dann wird das Programm mit dem return-Befehl neu gestartet.

Wurde alles passend verkabelt und das Programm auf den Arduino gespielt, sollte der Serielle Monitor nun bei jedem Druck auf eine Taste der Fernbedienung den passenden Code ausspucken.
Das klappt sogar auf Anhieb.

Dabei sind mir zwei Dinge aufgefallen, die bei der reinen Analyse mit dem Logic Analyzer nicht offensichtlich waren.
1. Die Fernbedieung sendet alle Signale jeweils 4mal - vermutlich um Übertragungsfehler auszuschließen
2. Der Signal zum hoch und runter fahren besteht jeweils aus zwei Befehlen: "hoch" sendet 4mal den schon bekannten Befehl "0F0F0100QQ0F100F 0F0F" gefolgt von 4mal "0F0F0100QQ0F100F 0F1Q" - "runter" ist entsprechend "0F0F0100QQ0F100F 0101" und "0F0F0100QQ0F100F 0110". Warum genau das so ist habe ich leider nicht heraus gefunden.

Im nächsten Beitrag werde ich beschreiben, wie man mit den so gewonnenen Erkenntnissen endlich den Rolladen steuern kann.