zumindest mit Licht funktioniert es super
Ich habe das Raspberry PI Projekt aus der c’t3/2014 nachgebaut. Dazu mal kurz meine Erfahrungen:
Das Ganze lässt sich theoretisch direkt aus der c’t nachbauen. Probleme tauchen aber dann schon auf, wenn man nicht die Funkempfänger verwendet die im Heft verwendet werden. Ich selber hatte noch 2 Funkempfänger von Intertechno ([hier] bei Bauhaus um die Ecke). Dank Papa „Google“ lies sich aber auch dafür zwei Lösungen finden. Die Intertechnogeräte gibt es in zwei Varianten - selbstlernend und mit Codierrad. Die Sender benötigen (bei meiner Version) zwingend ein Codierrad. Die andere Lösung funktioniert auch mit den fest codierten Empfängern (16 Mio. Codes fest vorgegeben). Als Empfänger funktionierten bei mir beide Varianten (Selbstlernend und mit Codierrad). Im grund braucht man die Sender nur um die aktuellen Lichtschalter durch Funkschalter zu ersetzen. Es sei den man ist so Nerdig wirklich alles nur noch über Tablett/Handy/PC zu steuern…
Benötigte Hardware
[ul]
[li]ein Raspberry PI (Model A oder B) - Spannungsversorgung nicht vergessen - teure SD-Karte kaufen Class 10 hat bei mir geholfen / Class 4 war immer Schrott
[/li][li]einen RF Sender für 432,92MHz
[/li][li]für Lötfaule: Drahtbrücken
[/li][/ul]
Den Raspberry entsprechend diverser Tutorials einrichten. Über die Konsole folgende Projekte installieren:
Zugriff auf die GPIO des Raspberry
cd ~
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
Codierung für die Funksender
cd ~
git clone https://github.com/r10r/rcswitch-pi.git
cd rcswitch-pi
make
Die Verbindung des Senders mit dem Raspberry ist auch schnell durch. Eine Pin-Belegung findet sich unter http://www.rn-wissen.de/index.php/Raspberry_PI:_GPIO. Dabei zu beachten das die Pinbezeichnung auf dem Board nicht mit den Pins am Prozessor identisch sind. GPIO-PIN 17 am Prozessor ist PIN 11 auf dem Board! Mit den Steckbrücken nun einfach die PIN den Boards mit den PIN des Senders verbinden. Der Sender nimmt 3V bis 12V. Ich habe vom Board einen 5V PIN genommen (mit den Steckbrücken):
[ul]
[li]GND: Board-Pin 6 → Sender Pin 1 (großes rundes Ding ist leicht link, Pins zeigen nach unten, SMD-Teile sind unter der Platine)
[/li][li]DATA: Board-Pin 11 → Sender Pin 2
[/li][li]Vcc: Board-Pin 2 → Sender Pin 3
[/li][li]Sender Pin 4 ist die Antene einfach ein Kabel dran hängen - bei mir machts auch ein weitere der Steckbrücken
[/li][/ul]
Theoretisch lässt sich nun so ein Empfänger schalten, falls man einen Empfänger mit DIP-Schalter hat. Dann sind die ersten 5 DIP-Schalter der Hauscode (0 → unten / 1 → oben) und die letzten 5 DIP-Schalter der Gerätecode. Auf der Console dann (im Verzeichnis rcswitch-pi) ./send haus gerät 1
zum Einschalten - ./send haus gerät 0
zum Ausschalten (sudo nicht vergessen).
Für Intertechno muss die send.cpp gepatcht werden (irgendwo in den Tiefen des Internets mit Papa „Google“ gefunden):
/*
Usage for Elro 440: ./send <systemCode> <unitCode> <command>
Usage for Intertechno: ./send <houseCode> <groupCode> <deviceCode> <command>
Command is 0 for OFF and 1 for ON
*/
#include "RCSwitch.h"
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
/*
output PIN is hardcoded for testing purposes
see https://projects.drogon.net/raspberry-pi/wiringpi/pins/
for pin mapping of the raspberry pi GPIO connector
*/
int PIN = 0;
if (wiringPiSetup () == -1) return 1;
RCSwitch mySwitch = RCSwitch();
mySwitch.enableTransmit(PIN);
if (argc==5) // find out whether command style intertechno
{
char* houseCode = argv[1];
int group = atoi(argv[2]);
int device = atoi(argv[3]);
int command = atoi(argv[4]);
printf("sending Intertechno format: houseCode[%c] group[%i] device[%i] command[%i]
", houseCode[0], group, device, command);
switch(command) {
case 1:
mySwitch.switchOn(houseCode[0], group, device);
break;
case 0:
mySwitch.switchOff(houseCode[0], group, device);
break;
default:
printf("command[%i] is unsupported
", command);
return -1;
}
}
else if(argc==4) // type elro440 controller
{
char* systemCode = argv[1];
int unitCode = atoi(argv[2]);
int command = atoi(argv[3]);
printf("sending Elro440 format: systemCode[%s] unitCode[%i] command[%i]
", systemCode, unitCode, command);
switch(command) {
case 1:
mySwitch.switchOn(systemCode, unitCode);
break;
case 0:
mySwitch.switchOff(systemCode, unitCode);
break;
default:
printf("command[%i] is unsupported
", command);
return -1;
}
}
else
{
printf(" Usage for Elro 440: ./send <systemCode> <unitCode> <command>
");
printf(" Usage for Intertechno: ./send <houseCode> <groupCode> <deviceCode> <command>
");
printf(" Command is 0 for OFF and 1 for ON
");
}
return 0;
}
Der groupCode
ist bei mir immer 1. der houseCode
ist der Buchstabe auf dem Codierrad und der deviceCode
ist entsprechend die Zahl des zweiten Codierrades.
*** Edit ***
so weit so langweilig
Als erstest stört das man den Send-Befehl nur aus dem Entwicklungs-Verzeichnis starten kann. Dann muss auch noch jedesmal das Passwort eingebene werden. Daher als erstes den Befehle kopieren und entsprechende Rechte setzen
cd ~/rcswitch-pi
sudo cp send /usr/bin/send
sudo chown root:root /usr/bin/send
sudo chmod 755 /usr/bin7send
Damit ist jetzt send
direkt aufrufbar und muss nicht über das Entwicklungsverzeichnis aufgerufen werden. Jetzt stört noch das man das Passwort eingeben muss. Dazu kann die Datei /etc/sudoers
angepasst werden sudo nano /etc/sudoers
. ACHTUNG !!! Das ist ein Sicherheitskritischer Bereich. An das Ende können dann entsprechende Benutzer freigegeben werden, die sudo OHNE Passwort verwenden dürfen. Ich habe das Ganze aber auf den Send-Befehl eingeschränkt:
pi ALL = NOPASSWD: /usr/bin/send
www-data ALL = NOPASSWD: /usr/bin/send
pi ist ja der Normale-User und www-data ist der User für den Apache. Damit kann der Befehl von Apache aus verwendet werden.
Nun will man ja nicht permanent auf die Konsole des Raspberry rum fummeln, das ist ja Unsinnig. Ergo sollte als erstes eine Weboberfläche her. Wer als Betriebssystem für Raspbian als Grundlage entschieden hat, hat Debian drauf. ubuntu basiert auch auf Debian und der Paketmanager wurde bei beiden Debianderivaten mit übernommen. Ein gute Hilfe ist also (auch) wiki.ubuntuusers.de.
Also einfach mal den Apache installieren sudo apt-get install apache2 php5
. Neben Apache ist auch PHP5 von Vorteil. Wer statt PHP lieber was anderes als Skriptsprache verwenden will, büdde. Die Webseite hat eher funktionalen Charakter, statt feinem MVC & Co. Somit alles Quick&Dirty
<?php
function initTable($title) {
echo "<table>";
echo "<tr>";
echo"<th colspan=\"4\">$title</th>";
echo "</tr>";
}
function quitTable() {
echo "</table>";
}
function showSwitch($name, $house, $device) {
echo "<tr>";
echo "<td width=\"50px\"> </td>";
echo "<td width=\"150px\">$name</td>";
echo "<td width=\"50px\"><a href=\"?switch&house=$house&device=$device&on\">Ein</a></td>";
echo "<td width=\"50px\"><a href=\"?switch&house=$house&device=$device&off\">Aus</a></td>";
echo "</tr>";
}
if (isset($_GET["switch"])) {
$house = $_GET["house"];
$device = $_GET["device"];
$mode = isset($_GET["on"]) == true ? 1 : 0;
$command = "sudo send $house 1 $device $mode";
$output = shell_exec($command);
//echo "COMMAND: $command<br/>";
//echo "RESULT: $output<br/>";
}
initTable("Kinderzimmer 1");
showSwitch("Treppe", "a", "1");
showSwitch("Decke, großes Licht", "a", "2");
quitTable();
initTable("Kinderzimmer 2");
showSwitch("Höhle", "a", "3");
showSwitch("Decke, großes Licht", "a", "4");
quitTable();
initTable("Wohnzimmer");
showSwitch("Fernsehlicht", "a", "5");
showSwitch("Decke, großes Licht", "a", "6");
quitTable();
Jetzt lässt sich das Ganze von jedem Browser aus steuern. Wenn ein entsprechende Plugin/Tool für den Desktop hat, kann die Webseite direkt auf dem Desktop legen und die Kinder ärgern
*** Edit ***
langsam wirds lustig
Nun kann man auf dem Tablet natürlich auch die Webseite aufrufen, das ist aber uncool. Da kann man auch eine App schreiben. Glücklicherweise ist ja schon eine Schnittstelle definiert. Die muss nur noch aufgerufen werden. Bei mir ist das natürlich Android. Es gilt das gleiche wie für PHP - alles Quick&Dirty. Erstmal das Layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kinderzimmer 1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdA11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickA11"
android:text="Ein" />
<Button
android:id="@+id/cmdA10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickA10"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Treppe"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdA21"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickA21"
android:text="Ein" />
<Button
android:id="@+id/cmdA20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickA20"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Decke, großes Licht"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdAMaster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickAMaster"
android:text="Master Aus" />
</LinearLayout>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="25dp"
android:text="Kinderzimmer 1"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdC11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickC11"
android:text="Ein" />
<Button
android:id="@+id/cmdC10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickC10"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Höhle"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdA21"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickC21"
android:text="Ein" />
<Button
android:id="@+id/cmdA20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickC20"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Decke, großes Licht"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdBMaster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickCMaster"
android:text="Master Aus" />
</LinearLayout>
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="25dp"
android:text="Wohnzimmer"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdC11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickE11"
android:text="Ein" />
<Button
android:id="@+id/cmdC10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickE10"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Fernsehlicht"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdA21"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickE21"
android:text="Ein" />
<Button
android:id="@+id/cmdA20"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickE20"
android:text="Aus" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:text="Decke, großes Licht"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="50dp"
android:minWidth="25dp" />
<Button
android:id="@+id/cmdEMaster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickEMaster"
android:text="Master Aus" />
</LinearLayout>
</LinearLayout>
Dann die Activity - entsprechende Rechte für INTERNET in der Manifest nicht vergessen
package de.x8bit.homeautomation;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// !!! Q&D Lösung !!!
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void connect(String link) throws Exception {
Log.i("HOME", "REQUEST: " + link);
HttpURLConnection con = (HttpURLConnection) new URL(link).openConnection();
con.connect();
if (con.getResponseCode() == HttpURLConnection.HTTP_OK) {
Log.i("HOME", inputStreamToString(con.getInputStream()));
} else {
Log.i("HOME", "Response: " + con.getResponseCode());
}
}
private String inputStreamToString(InputStream in) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
StringBuilder stringBuilder = new StringBuilder();
String line = null;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + "
");
}
bufferedReader.close();
return stringBuilder.toString();
}
private void onClickSwitch(char house, int device, boolean mode) {
//http://172.16.254.150/?switch&house=a&device=1&off
StringBuilder link = new StringBuilder("http://172.16.254.150/?switch&house=");
link.append(house);
link.append("&device=");
link.append(device);
link.append("&").append(mode ? "on" : "off");
try {
connect(link.toString());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static final boolean ON = true;
public static final boolean OFF = false;
// Joel
public void onClickA11(final View current) {
onClickSwitch('a', 1, ON);
}
public void onClickA10(final View current) {
onClickSwitch('a', 1, OFF);
}
public void onClickA21(final View current) {
onClickSwitch('a', 2, ON);
}
public void onClickA20(final View current) {
onClickSwitch('a', 2, OFF);
}
public void onClickAMaster(final View current) {
onClickSwitch('a', 1, OFF);
onClickSwitch('a', 2, OFF);
}
// Mica
public void onClickC11(final View current) {
onClickSwitch('c', 1, ON);
}
public void onClickC10(final View current) {
onClickSwitch('c', 1, OFF);
}
public void onClickC21(final View current) {
onClickSwitch('c', 2, ON);
}
public void onClickC20(final View current) {
onClickSwitch('c', 2, OFF);
}
public void onClickCMaster(final View current) {
onClickSwitch('c', 1, OFF);
onClickSwitch('c', 2, OFF);
}
// Wohnzimmer
public void onClickE11(final View current) {
onClickSwitch('e', 1, ON);
}
public void onClickE10(final View current) {
onClickSwitch('e', 1, OFF);
}
public void onClickE21(final View current) {
onClickSwitch('e', 2, ON);
}
public void onClickE20(final View current) {
onClickSwitch('e', 2, OFF);
}
public void onClickEMaster(final View current) {
onClickSwitch('e', 1, OFF);
onClickSwitch('e', 2, OFF);
}
}
Eigentlich wird nur jedesmal ein GET an den Raspberry gesendet. Der genaue Aufbau lässt sich ja aus dem Quellcode entnehmen. Damit hat man eine kleine App und kann nur bequem über alles mögliche das Licht steuern.