AdSense

Posts mit dem Label 433 MHz werden angezeigt. Alle Posts anzeigen
Posts mit dem Label 433 MHz werden angezeigt. Alle Posts anzeigen

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.

Montag, 12. August 2013

"Home Automation" mit dem Arduino und 433 MHz - Die Funksteckdose

(English version) Wie in meinem letzten Beitrag beschrieben, möchte ich mit den 433MHz-Funkmodulen Funksteckdosen ansteuern. Bei Pollin gibt es ein Set mit drei Steckdosen und einer Fernbedienung (die wir natürlich nicht brauchen werden ;) ) für unter 10 Euro: Klick mich! Zusätzliche Steckdosen können für 4,50€ zugekauft werden.

Für die Ansteuerung dieser Steckdosen gibt es (wie so oft) eine fertige Library für Arduino mit Namen rc-switch. Die Library kann sowohl Sender als auch Empfänger ansprechen. Mitgeliefert werden einige Beispiele, die das demonstrieren.

Übertragungsprotokoll

Bevor ich aber auf die Verwendung näher eingehe, möchte ich noch die Grundlagen des verwendeten Übertragungsprotokolls erläutern. Der Sender sendet im Prinzip nur eine Bitfolge in Form von HIGH- und LOW-Pegeln. Diese Bitfolge wird jedoch nicht als binäre Bitfolge (also nur 0 und 1), sondern als sogenannte Tri-State-Bitfolge ausgewertet. Wie der Name schon erahnen lässt, gibt es DREI ZUSTÄNDE (statt zwei bei rein binärer Übertragung). Die Zustände heißen 0, 1 und F, wobei F für "floating" steht. Dargestellt werden diese Zustände mit einer Folge HIGH-LOW-HIGH-LOW des Pegels. Die Abfolge von HIGH und LOW ist immer gleich, die Information wird über das Längenverhältnis von HIGH- und LOW-Phase übertragen.
Klingt vielleicht ein bisschen abstrakt, ist aber im Prinzip ganz einfach, wenn man es sich bildlich veranschaulicht:
ein Tri-State-Bit kann man sich wie zwei 4/4-Takte vorstellen. Jeweils eine HIGH-LOW-Folge entspricht einem Takt. Jeder Takt hat eine bestimmte Länge t. Der Takt wird in vier Teile mit der Länge t/4 geteilt. Nun gibt es zwei Möglichkeiten: entweder ist die HIGH-Phase länger als die LOW-Phase (3/4 der Zeit t HIGH und 1/4 LOW) oder umgekehrt.
Bildlich dargestellt:
 _ _ _                       _
|         | _  oder aber |   | _ _ _

Die drei Zustände werden dann folgendermaßen dargestellt:
 _            _
|   | _ _ _ |  | _ _ _ entspricht einer 0
 _ _ _       _ _ _                 
|         | _ |        | _ entspricht einer 1
 _            _ _ _
|   | _ _ _ |        | _ entspricht F

Drückt der Benutzer nun einen Knopf auf der Fernbedienung der Steckdosen, sendet diese zuerst die Adresse der gewünschten Steckdose sowie dem Befehl ("Ein" oder "Aus") im Tri-State-Format. Zum Schluss folgt ein Sync-Bit.

rc-switch und die Funksteckdosen

Mit dem Ganzen müssen wir uns aber eigentlich gar nicht auseinander setzen, denn wir haben ja rc-switch, unsere Library. Sie unterstützt alle möglichen Arten von Funksteckdosen von Haus aus.
Die Adressierung der Steckdosen erfolgt über DIP-Schalter:

Würde man die mitgelieferte Fernbedienung benutzen, müsste man hier mit den ersten fünf Schaltern den "System-Code" einstellen und mit den restlichen fünf, ob sich um Steckdose A, B, C, D oder E. handelt. In unserem Fall ist das aber egal, wir stellen einfach irgendeinen Code ein (in diesem Fall 11011'10000).

Nun bestromt man das Funkmodul, verbindet den DATA-Pin mit einem Pin des Arduinos und spielt folgendes Programm auf den Arduino:

#include <RCSwitch.h>

RCSwitch mySwitch = RCSwitch();

void setup() {

  //Sender ist mit Pin 10 verbunden  
  mySwitch.enableTransmit(10);
  
}

void loop() {

  //Einschalten
  //der erste Parameter ist die Stellung der ersten fünf DIP-Schalter, 
  //der zweite Parameter die Stellung der restlichen fünf
  mySwitch.switchOn("11011", "10000");

  //Warte...
  delay(1000);
  
  //Ausschalten
  mySwitch.switchOff("11011", "10000");
  
  //Warte...
  delay(1000);
  
}

Im setup() teilen wir dem Programm mit, an welchem Pin unser Sender hängt. Im loop() schalten wir die Steckdose dann zuerst an, warten eine Sekunde, schalten sie wieder aus, warten wieder eine Sekunde usw.
Der Befehl switchOn bzw. switchOff mit den Parametern wird von der Library intern in eine Tri-State-Bitfolge bestehend aus der Adresse und dem Ein- bzw. Aus-Befehl umgewandelt. Bei der Umwandlung wird es ein bisschen verwirrend, eine 1 im Parameter der obigen Befehle wird nämlich zu einer 0 im Tri-State-Befehl. Eine 0 wird zum F. Umgewandelt in Tri-State wäre die Adresse der verwendeten Steckdose nun also 00F00'0FFFF. Der Befehl zum Einschalten der Steckdose lautet 0F, der zum Ausschalten F0.

Man könnte also statt dem "fertigen" switchOn()-Befehl auch den sendTriState()-Befehl der Library verwenden, um den Code zum Schalten der Steckdose "von Hand" zu senden:

void loop() {

  //Einschalten
  mySwitch.sendTriState("00F000FFFF0F");

  //Warte...
  delay(1000);
  
  //Ausschalten
  mySwitch.sendTriState("00F000FFFFF0");
  
  //Warte...
  delay(1000);
  
}  

Wer diese ganze Tri-State-Geschichte nicht mag und lieber mit "echten" Bits arbeitet, kann das Tri-State-Format dann auch noch ins "normale" Bit-Format umrechnen:
00F00'0FFFF'0F entspräche dann 0000010000'0001010101'0001 und 00F00'0FFFF'0F wäre entsprechend 0000010000'0001010101'0100. Diese Bitfolgen könnten dann mit dem Befehl
mySwitch.send("000001000000010101010001");
übertragen werden. Da hier jedoch die Übersicht flöten geht, lasse ich das bleiben ;)

Die Funksteckdosen können wir nun also ansprechen, als nächstes ist nun der Rolladen dran. Das wird ein bisschen spannender ;) Mehr dazu im nächsten Beitrag.

Sonntag, 11. August 2013

"Home Automation" mit dem Arduino und 433 MHz - Vorgeplänkel

Neben den 2,4GHz-Funkmodulen, zu denen ich bereits einige Gast-Beiträge in diesem Blog veröffentlicht habe, beschäftige ich mich auch noch mit dem 433MHz-Band. Meine Erfahrungen hier möchte ich in den kommenden Posts teilen. 

Vorgeschichte

In meiner Wohnung wurden vor einem Jahr elektrisch betriebene Rolläden nachgerüstet. Die Rolläden werden über eine kleine Fernbedienung über Funk gesteuert. Das schreit ja geradezu nach "Home Automation". So könnte man die Rolläden z.B. automatisch herunterfahren lassen, wenn die Sonne ins Zimmer scheint und während der Ferienzeit könnten die Läden abends und morgens "zufällig" runter- bzw. hochgelassen werden, um Einbrecher abzuschrecken.

Ein Blick auf die Rückseite der Fernbedienung bringt folgendes zutage:

Besser hätte es eigentlich gar nicht laufen können, bei dem 433MHz-Band handelt es sich um ein sogenanntes ISM-Band (Industrial, Scientific and Medical Band). Hier sind allerlei Funkalarmanlagen, Funkfernbedienungen und Funksteckdosen unterwegs. Daher gibt es für den geneigten Bastler äußert billige Sender und Empfänger, mit denen er sich auf diesem Band vergnügen kann (Suche bei Ebay nach "433 mhz transmitter receiver" und du wirst finden).

Standard ist die folgende Empfänger-Sender-Kombi, die man schon für 1 Euro frisch aus China ordern kann:
Links zu sehen ist das Empfangsmodul, rechts das Sendemodul. Die Module haben jeweils drei Anschlüsse (beim Empfänger sind die beiden mittleren Pins verbunden): VCC, GND und Data. Die Funktion ist somit denkbar einfach. Wird am Data-Pin des Senders ein HIGH-Signal angelegt, sendet er ebenfalls ein HIGH-Signal. Entsprechend umgekehrt ist es beim Empfänger: empfängt er ein HIGH-Signal, liegt am Data-Pin HIGH an - sonst LOW. Damit lassen sich dann sehr einfach Bitfolgen übertragen.

Der Plan

Der Motor des Rolladen hat einen "normalen" Schuko-Stecker und wird in der Wohnung in eine freie Steckdose gesteckt. Mir ist schon vor einiger Zeit unangenehm aufgefallen, dass der Motor auch im Standy-Modus einige Watt Leistung zieht. In Zeiten steigender Strompreise natürlich ein Unding ;) Da sich die Steckdose aber irgendwo hinter meinem Schreibtisch befindet, war ich bisher immer zu faul, den Motor bei Nichtgebrauch auszustecken. Viel komfortabler wäre hier eine Funksteckdose.
Wie schon erwähnt, funken auch die meisten Funksteckdosen im 433MHz-Band. Es lassen sich hier also zwei Fliegen mit einer Klappe schlagen. Ziel ist es, ein System zu entwicklen, das sowohl Rolladen als auch Steckdose ansprechen kann.
In der Anwendung soll das dann in etwa so aussehen: möchte man den Rolladen schließen, drückt man den entsprechenden Knopf. Das System schaltet dann zuerst die Funksteckdose an. Dann bekommt der Motor sein Signal, um den Rolladen abzusenken. Ist der Vorgang abgeschlossen, wird die Funksteckdose wieder ausgeschaltet und der Bewohner kann mit gutem Gewissen ins Bett gehen.

Klingt theoretisch ganz einfach, zumindest der Teil mit der Steckdose ist es in der Praxis auch. Mit dem Rolladen wird es ein bisschen spannender, doch dazu in meinem nächsten Beitrag mehr.