Was lange währt, wird endlich gut … so, oder so ähnlich sagt man doch.
Jetzt endlich bin ich soweit und kann die Version 1.0 meiner Zeitschaltuhr präsentieren.
Warum hat es so viel länger gedauert als geplant?
Die vielen Änderungen und Erweiterungen die dazu gekommen sind haben mich immer wieder im Zeitplan zurück geworfen.
- Ich habe eine zusätzliche Tabelle mit den Daten für „Schalter“ eingeführt und diese mit der „scheduler“-Tabelle verbunden.
Dadurch muss ich z.Bsp. für neue Funkschalter nicht den python-Code ändern, sondern trage den neuen Funk-Code nur als neuen „Schalter“ in die neue Tabelle ein.
Allerdings habe ich noch kein UI für die zusätzliche „Schalter“-Tabelle erstellt, derzeit werden neue Schalter mit phpmyAdmin eingetragen. - Das Problem mit dem Ein-/Ausklappen des html-Menüs konnte ich beseitigen.
Der Grund war das Zusammenlegen von 2 x div’s – nachdem ich diese wieder getrennt habe funktioniert alles. - In der letzten Version hatte ich eine html-Seite für das Erstellen und eine extra html-Seite für das Ändern von Daten.
Zu 95% hatte ich dabei doppelten Code.
Die Webseite funktioniert jetzt mit nur einer Seite für das Erstellen & Ändern von Daten. - Ein kleines Extra ist die neue „Vorschau“-Funktion.
Hier werden, in chronologischer Reihenfolge, die zukünftigen Schaltvorgänge angezeigt.
Im Sourcecode kann ich einerseits einen Zeitraum angeben („+7 days“) und zum anderen eine feste Anzahl von Vorgängen.
Somit kann ich steuern wieviel Einträge maximal angezeigt werden (bei mir z.Bsp. die ersten 8 Einträge der nächsten 7 Tage). - Ich habe versucht alle Eingaben „sicher“ zu gestalten.
Das Einschleusen von javascript-Code sollte nicht mehr möglich sein. - Das python-Server-Skript wurde um Ausgabe-Funktionen für Tinkerforge-Relais, Tinkerforge-Funksender, eine USB-Steckdosenleiste und die raspi-GPIO-Pins erweitert worden.
Dazu passend gibt es auch ein youTube-Video.
Ganz am Ende des Beitrags findet Ihr übrigens den kompletten Sourcecode der Website und des python-Skripts zum downloaden.
Und hier findet Ihr die neue Online-Demo: html-test-area.mausbiber-projekte.de/scheduler
Jetzt erst einmal die Änderungen im Detail.
(1) Datenbank-Design
Wie schon erwähnt habe ich das Design der Datenbank geändert.
Anstatt wie zuvor mit nur einer Tabelle für alles zu arbeiten, habe ich nun 2 x Tabellen.
Das bringt mir gleich mehrere Vorteile.
Zum einen habe ich nun keine doppelten Daten mehr (Bsp. verschiedene Schaltzeiten mit dem gleichen Schalter) und zum anderen ist das Anlegen von neuen Schalter dadurch einfacher geworden.
Schließlich wird dadurch auch noch ein Teil der Schaltlogik vom python-Skript zur Datenbank transferiert.
Links auf dem Bild kann man den Aufbau der beiden Tabellen sehen, welche Feldtypen ich ausgewählt habe und wie die beiden Tabellen in Relation zueinander stehen.
Da ich noch nicht wissen kann, welche Parameter ich für zukünftige Schalter brauche und sich auch die bisherigen Schalter sehr unterscheiden habe ich dafür 4 x Felder (argA-D) angelegt.
Der Vorteil von dem Ganzen – ich kann z.Bsp. beliebig viele Funkempfänger über das Tinkerforge Bricklet ansprechen ohne den python-Code ändern zu müssen, gleiches gilt auch für die GPIO-Pins des Raspi.
(2) Fehlerbehebung: Menü Ein-/Ausklappen
Das war zwar kein richtiger Fehler, war aber optisch nicht gerade schön anzusehen.
Der Fehler kam dadurch zustande, das ich aus zwei DIV’s eins gemacht habe.
Anstatt
<!--Navigationsleiste--> <div id="sidebar-wrapper"> <ul class="sidebar-nav"> <li><a href="#">Dashboard</a></li> <li><a href="#">Zeitschaltuhr</a></li> <li><a href="#">Temperatur</a></li> </ul> </div> <div id="content-wrapper" class="container">
schreibe ich nun
<!--Navigationsleiste--> <div id="sidebar-wrapper"> <ul class="sidebar-nav"> <li><a href="#">Dashboard</a></li> <li><a href="#">Zeitschaltuhr</a></li> <li><a href="#">Temperatur</a></li> </ul> </div> <div id="content-wrapper"> <section class="container">
und schon wird beim Ein-/Ausklappen des Menüs der Inhaltsbereich sauber verschoben.
Kleiner Fehler, große Wirkung.
(3) Eine gemeinsame Seite für das Neu Anlegen & Ändern von Daten
Das hatte mich beim letzten Mal schon sehr gestört.
Vor allem aber musste ich jede Änderung im Layout doppelt machen – einfach unakzeptabel.
Das habe ich durch diverse Änderungen in der „update.php“ erreicht.
Zu Beginn überprüfe ich ob eine ID übergeben wurde, ist das nicht der Fall gehe ich vom Neuanlegen eines Schaltvorgangs aus, mit ID ist es eine Änderungen.
Mit diesem Wissen kann ich dann an den entsprechenden Stellen im Code arbeiten.
Dazu kommen noch ein paar kleine Änderungen in der „scheduler.data.inc.php“ und das war es auch schon.
(4) die Vorschau-Funktion
Wie sagt man so schön? Ein Bild sagt mehr als tausend Worte.
Aber Achtung: Zu dem Zeitpunkt an dem ich die Screenshots gemacht habe hat die Vorschau-Funktion noch nicht korrekt gearbeitet.
In jedem Fall gewinnt man schon einmal einen Eindruck wie das Ganze aussieht.
Die Vorschau-Funktion dient dazu, die nächsten Schaltungen anzuzeigen.
Wie bereits erwähnt, hat die Funktion am Anfang nicht korrekt gearbeitet. Zu Beginn wurde von jedem Eintrag nur der erste kommende Schaltvorgang angezeigt.
Nach vielen Umstellungen und Hilfe vom php.de-Forum funktioniert jetzt aber alles wie es soll.
Wieviel Datensätze angezeigt werden kann ich an 2 x Stellen beeinflussen.
Zum einen hat die Funktion ein Übergabeparameter „$vorschau“, hier mit übergebe ich beim Aufruf einen Vorschau-Zeitraum.
$events = $timer->scheduled_events_list("+14 days");
Als zweite Stellschraube kann ich in der „scheduler.next-widget.inc.php“ die Anzahl der dargestellten Schaltungen begrenzen.
if ($zaehler == 8) break;
In der Kombination heißt das, das nur die ersten 8 x Schaltvorgänge innerhalb der nächsten 14 Tage angezeigt werden.
Technisch funktioniert die Funktion folgendermaßen:
Ich gehe nach und nach jeden Eintrag in der Tabelle „scheduler“ durch, nehme das Startdatum und schaue mir den Wiederholungs-Intervall an (einmalig, interval oder wochentag).
Dann addiere ich solange den Wiederholungs-Zeitraum auf das Startdatum (diese Daten werden verworfen) bis ich beim aktuellen Datum angekommen bin.
Nun addiere ich wieder solange den Wiederholungs-Zeitraum auf das Datum bis ich bei dem Ende des vorgegebenen Vorschau-Zeitraums angekommen bin.
Diese Daten speichere ich nun aber in einer Liste ab (Datum, ID des Vorgangs, etc…).
Zu guter Letzt sortiere ich dann die Liste noch einmal nach dem Datum und fertig.
Natürlich muss ich noch ein paar andere Sachen bedenken, wie z.Bsp. ein vorgegebenes End-Datum (z.Bsp. soll ein Schaltprogramm nur 6 Wochen laufen), Vorgänge die einmalig sind oder in der Zukunft liegen.
Irgendwann hatte ich dann aber eine halbwegs funktionierende Vorschau-Funktion fertig und jetzt kommt das php.de-Forum ins Spiel.
Die Leute dort haben mir sehr dabei geholfen den php-Code zu verkürzen und zu vereinfachen.
Diese Funktion hat mich am längsten aufgehalten, vor allem weil sich am Ende noch ein Fehler bei der php-Funktion „DateTime:modify“ eingeschlichen hatte.
Die entsprechende Funktion steht erst ab php 5.3 zur Verfügung (mit kleineren Versionen funktioniert meine Software nicht), ändert Ihr Verhalten aber ab Version 5.3.6.
Zuhause hatte ich die Version 5.3.3 und bei Strato 5.3.6 – das hatte so seltsamen Unterschieden geführt und mich enorm viel Zeit gekostet.
Abschließend habe ich aber auch dieses Problem gelöst und die Vorschau-Funktion arbeitet jetzt wie gewünscht.
(5) Sicherheit bei Benutzereingaben
Bei der letzten Version der Zeitschaltuhr wurden die Eingaben ungeprüft in die mySQL-Datenbank übernommen.
Dadurch konnte an mehreren Stellen Code per SQL-Injection eingeschleust werden.
Unter anderem konnte man javascript direkt in das Bezeichnungs-Feld eintragen und dadurch die komplette Weboberfläche zum Erliegen bringen.
Auch wenn die Benutzeroberfläche eigentlich nur für den Gebrauch im heimischen Netzwerk gedacht darf ein Mindestmaß an Sicherheit nicht fehlen.
Zu Beginn war das kein Thema für mich, da nur ich die Software bediene – trotzdem habe ich hier nachgebessert.
Ich habe mich im Internet informiert, auf welche Weise ich mich am besten absichern kann und bin dann auf „prepared statements“ gestoßen.
Eingebaut haben ich das dann so wie hier …
$stmt = $this->conn->prepare( "SELECT switches.id FROM switches WHERE switches.title = ? " ); $stmt->execute((array($this->switchCommand)));
… zusätzlich nutze ich beim Neu Anlegen eines Datensatzes die „filter_var“-Funktion um ganz auf Nummer sicher zu gehen …
$query = "INSERT INTO schedulers values('',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; $stmt = $this->conn->prepare($query); ... ... $this->title = filter_var($this->title); if ($stmt->execute(array($this->title, $this->switchesID, $this->dateStartOn, $this->dateStartOff, $this->dateStop, $this->dateStopOn, $this->dateStopOff, $this->duration, $this->intervalNumber, $this->intervalUnit, $this->weeklyMonday, $this->weeklyTuesday, $this->weeklyWednesday, $this->weeklyThursday, $this->weeklyFriday, $this->weeklySaturday, $this->weeklySunday))) { ... ...
Mit diesen beiden Änderungen sollte ich jetzt auf der sicheren Seite sein.
SQL-Injections dürften nicht mehr möglich sein.
Hier würde ich gerne eure Meinung wissen!
Reichen meine Änderungen und ist die Seite so jetzt sicher?
(6) Ausgabe-Funktionen im python-Skript
Wie schon im letzten Beitrag erwähnt, habe ich das python-Skript nun um praxisnahe Ausgabefunktionen erweitert.
Im Einzelnen sind das eine Funktion zur Ansteuerung des Tinkerforge Remote Switch Bricklet (Funksender), des Tinkerforge Dual Relay Bricklets, einer über USB steuerbaren Steckdosenleiste und der GPIO-Pins bei einem Raspberry Pi.
Wie die einzelnen Teile in einer Praxis-Demo funktionieren ist in meinem youTube-Video zu sehen.
Der Sourcecode für die Ausgabe-Funktionen:
def action(title, scheduler_id, switch_to, date_stop, switch_typ, arg_a, arg_b, arg_c, arg_d): check_date_stop(scheduler_id, switch_to, date_stop) if switch_typ == "tf_remote": action_tf_remote(title, switch_to, arg_a, arg_b, int(arg_c)) elif switch_typ == "tf_dual": action_tf_dual_relay(title, switch_to, arg_a) elif switch_typ == "usb_socket": action_usb_socket(title, switch_to, int(arg_a)) elif switch_typ == "gpio": action_gpio(title, switch_to, int(arg_a)) def action_tf_remote(title, switch_to, remote_type, remote_code_a, remote_code_b): logger.info('Timerswitch ... %s (tf_remote) : %s' % (title, switch_to)) switch_to = 1 if switch_to else 0 bricklet_tf_remote.send(remote_type, remote_code_a, remote_code_b, switch_to) def action_tf_dual_relay(title, switch_to, relay): logger.info('Timerswitch ... %s (tf_dual) : %s' % (title, switch_to)) switch_to = 1 if switch_to else 0 if relay == "a": bricklet_dual_relay.a = switch_to elif relay == "b": bricklet_dual_relay.b = switch_to def action_usb_socket(title, switch_to, number): logger.info('Timerswitch ... %s (usb_socket) : %s' % (title, switch_to)) usb_steckdose.set_outlet_enabled(number, switch_to) def action_gpio(title, switch_to, gpio_pin): logger.info('Timerswitch ... %s (gpio) : %s' % (title, switch_to)) GPIO.setup(gpio_pin, GPIO.OUT) if switch_to: GPIO.output(gpio_pin, GPIO.HIGH) else: GPIO.output(gpio_pin, GPIO.LOW)
Im Prinzip habe ich es möglichst einfach gehalten.
Anhand des „switch_typ“ wähle ich die entsprechende Funktion aus, sofern notwendig wird der Variabel-Typ umgewandelt und dann übergebe ich die notwendigen Parameter an die eigentlichen Ausgabe-Funktionen.
Hier kann man auch sehen wofür die Tabellen-Felder in der neuen „Schalter“-Tabelle gedacht sind und was ich damit gemeint habe das ein Teil der Logik zur Datenbank gewandert ist.
Ich kann verschiedene GPIO-Pins und Funk-Empfänger als Schalter anlegen und auch ansprechen ohne den python-Sourcecode zu ändern.
Ansonsten hat sich beim python-Skript kaum etwas geändert.
Es mussten lediglich die mysql-Abfragen um die neue „Schalter“-Tabelle erweitert werden.
Aussicht
So, was kommt als nächstes?
Für die Zukunft habe ich erst einmal noch 3 x Verbesserungen auf dem Programm:
- Das alte Problem mit dem Überprüfen ob der python-Server läuft.
Meine bisherige Lösung mit javascript & websockets finde ich alles andere als gelungen.
Also, wer weiß hier Rat, wie kann ich den Bereich verbessern??? - Für die neue „Schalter“-Tabelle brauche ich noch eine Listen-Ansicht und die Möglichkeit der Eingabe/des Änderns über die Benutzeroberfläche.
Bisher war ich schlichtweg zu faul das zu programmieren, vor allem weil es derzeit per Hand (und pypmyAdmin) so schön einfach & schnell geht. - Ich würde die Schaltzeit-Möglichkeiten gerne um den Punkt „Schalten bei Sonnenaufgang/Sonnenuntergang“ erweitern.
Anstatt eine Start-/Endzeit einzugeben, kann man einfach den Sonnenaufgang als Anschaltzeit und den Sonnenuntergang als Ausschaltzeit nehmen (oder umgekehrt).
Gerade für die Pflanzenbeleuchtung sollte diese Variante nützlich sein.
Anhang
Download: Zeitschaltuhr v1.0 – website
Download: Zeitschaltuhr v1.0 – python sourcecode
youTube-Video: youTube-Video
Demo-Website: html-test-area.mausbiber-projekte.de/scheduler
Moin Moin,
sehr schicke Umsetzung. Ich bastel schon seid ein paar Jahren an etwas ähnlichem. Habe damals mit nem Arduino (https://github.com/vaddi/ATM-Arduino-Temperature-Mailer-Daemon) angefangen und bin mittlerweile auch beim Raspi gelandet (https://github.com/vaddi/biotopi). Hab dein Projekt gefunden, weil ich genau an der Zeitschaltfunktion schon lange grübel und noch keine brauchbare Lösung fand. Vielleicht kann man sich ja gegenseitig inspirieren. Dein Ansatz schaut zumindest sehr viel versprechend aus. Hier noch ein paar Tips zu deinen Fragen:
PHP hat zum ermitteln von Sonnen auf und Untergang, zwei Funktionen eingebaut:
date_sunset
date_sunrise
Du mußt u.a. nur Längen- und Breitengrad Deines Standorts übergeben.
Zum prüfen ob ein Prozess noch läuft, schau nach ob die PID des Prozesses noch aktiv ist: http://stackoverflow.com/a/14880926
Statt phpmyadmin, empfehle ich dir https://www.adminer.org/de/ mal anzuschauen.
Und bleib weiter an git dran! Dazu reichen auch schon die Basics (https://git-scm.com/book/de/v1, ersten 2 Kapitel). Es Erleichtert ungemein den Überblick zu behalten, Versionen zu pflegen (tagging) und im Team zu arbeiten. Beim „branching“ hast du etwas missverstanden, du kannst die beiden nicht direkt nebeneinander „anbieten/verwenden“. Es ist eher als eine Hilfe beim Entwickeln zu verstehen. Jedes Entwicklerteam (oder Entwickler) kann seinen eigenen „Arbeitszweig“ erstellen um an etwas zu arbeiten. Die Zweige werden danach wieder mit dem Hauptzweig zusammengeführt (mergen) um eine Version zu erhalten, die man veröffentlichen (deployen) kann. Daher ist dein Ansatz das einfach alles in einer Ordnerstruktur zusammen zu halten völlig ok.
Besten Gruß
mvattersen
Hallo,
danke erstmal für das Super Tutorial. Ich möchte eine ähnliche Zeitschaltuhr für mein aktuelles Projekt umsetzen. Ich hoffe ihr könnt mir helfen. Ich benötige eigentlich nur das Backend wo die GPIO Pins geschaltet werden. Ich habe bereits ein VB.Net Oberfläche gestaltet und entwickelt. Könnt ihr mir sagen wie ich das umsetzen kann?
Mfg,
Manuel
Hallo,
erstmal herzlichen Glückwunsch zu deinem tollen Projekt! Sieht echt klasse aus…..
Ich hätte da aber trotzdem mal ne Frage zu! Ich möchte mit meinem Raspi 4 Rollos zeitgesteuert laufen lassen, oder evtl. auch per Tastendruck (so ist es im Moment – mit Tastendruck) !!! Wäre das mit deinem Prog. möglich?
Vielen Dank schonmal
Stefan
Also im Moment noch nicht, da derzeit nur zeitgesteuert geschaltet werden kann.
In der nächsten Version wird es aber die Möglichkeit geben Schalter per html-frontend manuell zu schalten.
Also wenn dir ein Schalter auf dem Smartphone/Tablet/PC ausreicht dann geht das mit der nächsten Version.
Wenn du einen Hardware-Schalter brauchst, dann geht das auch, du müsstest aber selbst im client-code eine entsprechende Ereignissabfrage einbauen.
Hallo Zusammen da ich ebenfalls neu in der Raspberry pi und Linux bin würde ich mich über ein kleines wiki oder eine Install-Anleitung freuen. Da ich bei mir ebenfalls Terrarien habe und diese mit deinem intressanten Projekt überwachen und schalten möchte
Gruss Mark
Im Augenblick habe ich noch keine Installationsroutine, zum Thema Installation werde ich aber heute noch einen Beitrag veröffentlichen.
Die Möglichkeit ein Terrarium zu steuern wird irgendwann noch kommen.
Ein Interface zur Temperatursteuerung, samt PID-Reglung in python hatte ich Anfang des Jahres mal testweise im Einsatz.
Hallo, cooles Projekt,
wie kann ich denn das auf meinen raspberry auch machen? Das Ding ist heute mit der Post gekommen und ich bin schon voll gespannt!
Vielen Dank
Clemens
Da wäre die erste Frage wie gut du dich mit python auskennst, bzw. was genau du damit schalten willst?
Ansonsten versuche ich am Wochenende mal eine wirklich ganz kurze Installationsanleitung auf die github-Seite zu packen.
ich will eigentlich folgendes machen: Bei uns auf dem Parkplatz stehen 3 Autos im Freien. Wenn es jetzt schneit, sind alle Autos zugefrohren. Ich würde gerne an den Raspi 3 Relais hinhängen(hab ich schon am GPIO) und diese dann schalten. Der Raspi ist mit Netzwerk mit unserem Haus verbunden. Somit könnte jeder unserer Mitbewohner über die Weboberfläche einstellen, wenn der Heizlüfter, der im Auto ist und an der Steckdose (Relais) anschlossen ist, wenn der Lüfter losgehen soll. Evtl. mehrmals in der Nacht, weil es sonst ewig dauert, bis die Karre bzw. die Scheiben frei sind. So ist es halt bei uns im Allgäu
VG
clemens
Ja, das wäre damit wohl möglich.
Zum Thema Installation werde ich heute noch einen Beitrag veröffentlichen.
Schönes Projekt, ich bin versucht ev. dazu beizutragen 😀
Nur fehlt für mich eine einfache Möglichkeit, den code anzupassen. Hast du schon mal darüber nachgedacht, ihn auf Guthub zu stellen? Dann kann jeder einfach an dem code arbeiten & du hast eine einfach zu bedienende Versionskontrolle!
Das würde mich natürlich sehr freuen, Mitarbeit ist gerne gesehen.
Und ja, an Github gedacht habe ich schon öfters.
Ich habe es sogar mehrmals versucht, blicke aber bei github anscheinend nicht ganz durch.
Wenn mir jemand da mal ne Art Anleitung zu geben könnten.
Das ist ziemlich einfach, siehe z.B. hier
https://help.github.com/articles/create-a-repo/
Alternativ schreib mir mal ne Mail 😀
Hmm, ich werde es am Wochenende mal versuchen.
Ich will mir aber vorher über die Struktur klar werden.
Also welche Ordner, wie, wo, was 😉
Okay, also das Grundprinzip von github habe ich wohl verstanden.
Wie schon gesagt mache ich mir Gedanken wie ich das Ganze aufbauen soll.
Wenn würde ich dort schon gerne direkt mein smartHome-Projekt hosten.
Die Zeitschaltuhr hier ist nur eine Teilmenge des Projekts und noch dazu für den „standalone“-Betrieb abgewandelt.
Hier ein Bild vom IST-Zustand
Und hier wie die Struktur eigentlich aufgebaut ist, der SOLL-Zustand
Dabei muss man beachten das die Zeitschaltuhr hier quasi eine Mischung aus Client & Server ist.
Wie bilde ich das bei github ab?
Welche Ordnerstruktur?
Soll ich einen Branch nutzen?
Meine Idee wäre ein repository „smartHome“ mit einem Branch „standalone-scheduler“ – gut?
Nur bei der Ordnerstruktur komme ich nicht weiter, vorallem weil der „standalone-scheduler“ die gleichen libs wie der „Haupt“-Zweig nutzen soll.
Irgendwo müssen dann auch noch die html-Files untergebracht werden.
Vielleicht sehe ich auch gerade den Wald vor lauter Bäumen nicht.
So, ich habe mal einen ersten Versuch gestartet
https://github.com/Mausbiber/smartHome
Das sieht doch schon mal gut aus, ich werde es mir mal genauer anschauen 🙂
Danke, würde mich wirklich sehr freuen.