Nun ist es endlich soweit, ich kann auch Teil 2 präsentieren, inklusiv python Sourcecode und html-Frontend.
Perfekt ist das Ganze noch nicht, im html-Frontend stecken auch noch 2-3 Fehler.
Deshalb hoffe ich auf Eure Hilfe. Schreibt ein Kommentar und sagt mir wo Fehler sind.
Oder noch besser, schreibt mir wo ich den Code verbessern kann – ich kann wirklich jede Hilfe gebrauchen.
Aber fangen wir an.
Im 1. Teil habe ich mich nur mit der Eingabemaske für das Neuanlegen beschäftigt.
Damit alleine ist es aber nicht getan, wir brauchen natürlich noch Seiten für das Anzeigen aller Datensätze, zum Ändern und zum Löschen.
Außerdem sollte das Design Responsive sein, somit nutze ich bootstrap als html-Framework.
Den php-Unterbau habe ich von dieser Seite und dann entsprechend meinen Bedürfnissen erweitert.
Anstatt Screenshots zu nutzen, habe ich mich entschieden ein funktionierenden Frontend hier zu installieren.
Grundsätzlich arbeitet das Ganze nach dem CRUD-Prinzip:
html-test-area.mausbiber-projekte.de
Und hier kommt der Code, zur besseren Übersicht aufgeteilt in einen php-, html- und python-Abschnitt.
- Grundlage für das Ganze ist eine php Klasse
data.inc.php
<?php class Data{ private $conn; private $table_name = "schedulers"; public $id; public $title; public $device; public $strDateStart; public $strDateStop; public $strTimeStart; public $strTimeStop; public $dateStartOn; public $dateStartOff; public $dateStop; public $dateStopOn; public $dateStopOff; public $duration; public $intervalNumber; public $intervalUnit; public $weeklyMonday; public $weeklyTuesday; public $weeklyWednesday; public $weeklyThursday; public $weeklyFriday; public $weeklySaturday; public $weeklySunday; public function __construct($db){ $this->conn = $db; } function create(){ //write query $query = "INSERT INTO " . $this->table_name . " values('',?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; $stmt = $this->conn->prepare($query); $this->dateStartOn = date("Y-m-d H:i:s", strtotime(($this->strDateStart)." ".($this->strTimeStart))); $tmpHourOn = date("H", strtotime($this->strTimeStart)); $tmpHourOff = date("H", strtotime($this->strTimeStop)); $tmpMinuteOn = date("i", strtotime($this->strTimeStart)); $tmpMinuteOff = date("i", strtotime($this->strTimeStop)); if (($tmpHourOff < $tmpHourOn) or (($tmpHourOff == $tmpHourOn) and ($tmpMinuteOff < $tmpMinuteOn))) { $this->dateStartOff = date("Y-m-d H:i:s", strtotime("+1 day", strtotime(($this->strDateStart)." ".($this->strTimeStop)))); } else { $this->dateStartOff = date("Y-m-d H:i:s", strtotime(($this->strDateStart)." ".($this->strTimeStop))); } if (!$this->strDateStop) { $this->dateStop = 0; } else { $this->dateStop = 1; $this->dateStopOn = date("Y-m-d H:i:s", strtotime(($this->strDateStop)." ".($this->strTimeStart))); if (($tmpHourOff < $tmpHourOn) or (($tmpHourOff == $tmpHourOn) and ($tmpMinuteOff < $tmpMinuteOn))) { $this->dateStopOff = date("Y-m-d H:i:s", strtotime("+1 day", strtotime(($this->strDateStop)." ".($this->strTimeStop)))); } else { $this->dateStopOff = date("Y-m-d H:i:s", strtotime(($this->strDateStop)." ".($this->strTimeStop))); } } switch ($this->duration) { case 'einmalig': $this->intervalNumber = 0; $this->intervalUnit = ''; $this->weeklyMonday = 0; $this->weeklyTuesday = 0; $this->weeklyWednesday = 0; $this->weeklyThursday = 0; $this->weeklyFriday = 0; $this->weeklySaturday = 0; $this->weeklySunday = 0; break; case 'intervall': if (!$this->intervalNumber) {$this->intervalNumber = 0;} $this->weeklyMonday = 0; $this->weeklyTuesday = 0; $this->weeklyWednesday = 0; $this->weeklyThursday = 0; $this->weeklyFriday = 0; $this->weeklySaturday = 0; $this->weeklySunday = 0; break; case 'wochentag': $this->intervalNumber = 0; $this->intervalUnit = ''; if ($this->weeklyMonday == true) {$this->weeklyMonday=1; } else {$this->weeklyMonday=0;} if ($this->weeklyTuesday == true) {$this->weeklyTuesday=1; } else {$this->weeklyTuesday=0;} if ($this->weeklyWednesday==true) {$this->weeklyWednesday=1; } else {$this->weeklyWednesday=0;} if ($this->weeklyThursday==true) {$this->weeklyThursday=1; } else {$this->weeklyThursday=0;} if ($this->weeklyFriday==true) {$this->weeklyFriday=1; } else {$this->weeklyFriday=0;} if ($this->weeklySaturday==true) {$this->weeklySaturday=1; } else {$this->weeklySaturday=0;} if ($this->weeklySunday==true) {$this->weeklySunday=1; } else {$this->weeklySunday=0;} break; } $stmt->bindParam(1, $this->title); $stmt->bindParam(2, $this->device); $stmt->bindParam(3, $this->dateStartOn); $stmt->bindParam(4, $this->dateStartOff); $stmt->bindParam(5, $this->dateStop); $stmt->bindParam(6, $this->dateStopOn); $stmt->bindParam(7, $this->dateStopOff); $stmt->bindParam(8, $this->duration); $stmt->bindParam(9, $this->intervalNumber); $stmt->bindParam(10, $this->intervalUnit); $stmt->bindParam(11, $this->weeklyMonday); $stmt->bindParam(12, $this->weeklyTuesday); $stmt->bindParam(13, $this->weeklyWednesday); $stmt->bindParam(14, $this->weeklyThursday); $stmt->bindParam(15, $this->weeklyFriday); $stmt->bindParam(16, $this->weeklySaturday); $stmt->bindParam(17, $this->weeklySunday); if($stmt->execute()){ $query = "SELECT * FROM schedulers ORDER BY scheduler_id DESC LIMIT 1 ;"; $stmt = $this->conn->prepare($query); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row['scheduler_id']; }else{ return false; } } function readAll($page, $from_record_num, $records_per_page){ $query = "SELECT * FROM " . $this->table_name . " ORDER BY title ASC LIMIT {$from_record_num}, {$records_per_page}"; $stmt = $this->conn->prepare( $query ); $stmt->execute(); return $stmt; } public function countAll(){ $query = "SELECT scheduler_id FROM " . $this->table_name . ""; $stmt = $this->conn->prepare( $query ); $stmt->execute(); $num = $stmt->rowCount(); return $num; } function readOne(){ $query = "SELECT * FROM " . $this->table_name . " WHERE scheduler_id = ? LIMIT 0,1"; $stmt = $this->conn->prepare( $query ); $stmt->bindParam(1, $this->id); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); $this->title = $row['title']; $this->device = $row['device']; $this->dateStartOn = $row['date_start_on']; $this->dateStartOff = $row['date_start_off']; $this->dateStop = $row['date_stop']; $this->dateStopOn = $row['date_stop_on']; $this->dateStopOff = $row['date_stop_off']; $this->duration = $row['duration']; $this->intervalNumber = $row['interval_number']; $this->intervalUnit = $row['interval_unit']; $this->weeklyMonday = $row['weekly_monday']; $this->weeklyTuesday = $row['weekly_tuesday']; $this->weeklyWednesday = $row['weekly_wednesday']; $this->weeklyThursday = $row['weekly_thursday']; $this->weeklyFriday = $row['weekly_friday']; $this->weeklySaturday = $row['weekly_saturday']; $this->weeklySunday = $row['weekly_sunday']; $this->strDateStart = date("d.m.Y", strtotime($this->dateStartOn)); $this->strTimeStart = date("H:i", strtotime($this->dateStartOn)); $this->strTimeStop = date("H:i", strtotime($this->dateStartOff)); if ($this->dateStop) { $this->strDateStop = date("d.m.Y", strtotime($this->dateStopOn)); } } function update(){ $query = "UPDATE " . $this->table_name . " SET title = :title, device = :device, date_start_on = :dateStartOn, date_start_off = :dateStartOff, date_stop = :dateStop, date_stop_on = :dateStopOn, date_stop_off = :dateStopOff, duration = :duration, interval_number = :intervalNumber, interval_unit = :intervalUnit, weekly_monday = :weeklyMonday, weekly_tuesday = :weeklyTuesday, weekly_wednesday = :weeklyWednesday, weekly_thursday = :weeklyThursday, weekly_friday = :weeklyFriday, weekly_saturday = :weeklySaturday, weekly_sunday = :weeklySunday WHERE scheduler_id = :id"; $stmt = $this->conn->prepare($query); $this->dateStartOn = date("Y-m-d H:i:s", strtotime(($this->strDateStart)." ".($this->strTimeStart))); $tmpHourOn = date("H", strtotime($this->strTimeStart)); $tmpHourOff = date("H", strtotime($this->strTimeStop)); $tmpMinuteOn = date("i", strtotime($this->strTimeStart)); $tmpMinuteOff = date("i", strtotime($this->strTimeStop)); if (($tmpHourOff < $tmpHourOn) or (($tmpHourOff == $tmpHourOn) and ($tmpMinuteOff < $tmpMinuteOn))) { $this->dateStartOff = date("Y-m-d H:i:s", strtotime("+1 day", strtotime(($this->strDateStart)." ".($this->strTimeStop)))); } else { $this->dateStartOff = date("Y-m-d H:i:s", strtotime(($this->strDateStart)." ".($this->strTimeStop))); } if (!$this->strDateStop) { $this->dateStop = 0; } else { $this->dateStop = 1; $this->dateStopOn = date("Y-m-d H:i:s", strtotime(($this->strDateStop)." ".($this->strTimeStart))); if (($tmpHourOff < $tmpHourOn) or (($tmpHourOff == $tmpHourOn) and ($tmpMinuteOff < $tmpMinuteOn))) { $this->dateStopOff = date("Y-m-d H:i:s", strtotime("+1 day", strtotime(($this->strDateStop)." ".($this->strTimeStop)))); } else { $this->dateStopOff = date("Y-m-d H:i:s", strtotime(($this->strDateStop)." ".($this->strTimeStop))); } } switch ($this->duration) { case 'einmalig': $this->intervalNumber = 0; $this->intervalUnit = ''; $this->weeklyMonday = 0; $this->weeklyTuesday = 0; $this->weeklyWednesday = 0; $this->weeklyThursday = 0; $this->weeklyFriday = 0; $this->weeklySaturday = 0; $this->weeklySunday = 0; break; case 'intervall': if (!$this->intervalNumber) {$this->intervalNumber = 0;} $this->weeklyMonday = 0; $this->weeklyTuesday = 0; $this->weeklyWednesday = 0; $this->weeklyThursday = 0; $this->weeklyFriday = 0; $this->weeklySaturday = 0; $this->weeklySunday = 0; break; case 'wochentag': $this->intervalNumber = 0; $this->intervalUnit = ''; if ($this->weeklyMonday == true) {$this->weeklyMonday=1; } else {$this->weeklyMonday=0;} if ($this->weeklyTuesday == true) {$this->weeklyTuesday=1; } else {$this->weeklyTuesday=0;} if ($this->weeklyWednesday==true) {$this->weeklyWednesday=1; } else {$this->weeklyWednesday=0;} if ($this->weeklyThursday==true) {$this->weeklyThursday=1; } else {$this->weeklyThursday=0;} if ($this->weeklyFriday==true) {$this->weeklyFriday=1; } else {$this->weeklyFriday=0;} if ($this->weeklySaturday==true) {$this->weeklySaturday=1; } else {$this->weeklySaturday=0;} if ($this->weeklySunday==true) {$this->weeklySunday=1; } else {$this->weeklySunday=0;} break; } $stmt->bindParam(':title', $this->title); $stmt->bindParam(':device', $this->device); $stmt->bindParam(':dateStartOn', $this->dateStartOn); $stmt->bindParam(':dateStartOff', $this->dateStartOff); $stmt->bindParam(':dateStop', $this->dateStop); $stmt->bindParam(':dateStopOn', $this->dateStopOn); $stmt->bindParam(':dateStopOff', $this->dateStopOff); $stmt->bindParam(':duration', $this->duration); $stmt->bindParam(':intervalNumber', $this->intervalNumber); $stmt->bindParam(':intervalUnit', $this->intervalUnit); $stmt->bindParam(':weeklyMonday', $this->weeklyMonday); $stmt->bindParam(':weeklyTuesday', $this->weeklyTuesday); $stmt->bindParam(':weeklyWednesday', $this->weeklyWednesday); $stmt->bindParam(':weeklyThursday', $this->weeklyThursday); $stmt->bindParam(':weeklyFriday', $this->weeklyFriday); $stmt->bindParam(':weeklySaturday', $this->weeklySaturday); $stmt->bindParam(':weeklySunday', $this->weeklySunday); $stmt->bindParam(':id', $this->id); // execute the query if($stmt->execute()){ return true; }else{ return false; } } function delete(){ $query = "DELETE FROM " . $this->table_name . " WHERE scheduler_id = ?"; $stmt = $this->conn->prepare($query); $stmt->bindParam(1, $this->id); if($result = $stmt->execute()){ return true; }else{ return false; } } } ?>
Im Prinzip habe ich alles von dem Internet-Beispiel übernommen und nur die Felder entsprechend angepasst.
Interessant hier ist nur die Umwandlung der Datum- und Zeitangabe.
Diese Daten werden logischerweise als datetime-Objekt in mysql gespeichert.
Die Date- und Timepicker im Frontend arbeiten jedoch mit Strings.
Deshalb musste ich mit Extra-Feldern arbeiten und die Datum- und Zeitangaben entsprechend hin und her wandeln.
Ansonsten gehören dann noch die folgenden 2 x php-Dateien dazu.
Die erste stellt die Verbindung zur Datenbank dar und die zweite kümmert sich um die Pagination.
config.php
<?php class Config{ private $host = "192.168.1.1"; private $db_name = "db_name"; private $username = "db_user"; private $password = "db_password"; public $conn; public function getConnection(){ $this->conn = null; try{ $this->conn = new PDO("mysql:host=" . $this->host . ";dbname=" . $this->db_name, $this->username, $this->password); }catch(PDOException $exception){ echo "Connection error: " . $exception->getMessage(); } return $this->conn; } } ?>
pagination.inc.php
<?php echo "<nav><ul class=\"pagination\">"; if($page>1){ echo "<li><a href='{$page_dom}' title='Go to the first page.'>"; echo "«"; echo "</a></li>"; } $total_rows = $product->countAll(); $total_pages = ceil($total_rows / $records_per_page); $range = 2; $initial_num = $page - $range; $condition_limit_num = ($page + $range) + 1; for ($x=$initial_num; $x<$condition_limit_num; $x++) { if (($x > 0) && ($x <= $total_pages)) { if ($x == $page) { echo "<li class='active'><a href=\"#\">$x</a></li>"; } else { echo "<li><a href='{$page_dom}?page=$x'>$x</a></li>"; } } } if($page<$total_pages){ echo "<li><a href='" .$page_dom . "?page={$total_pages}' title='Last page is {$total_pages}.'>"; echo "»"; echo "</a></li>"; } echo "</ul></nav>"; ?>
Als Grundlage nutze ich das html5-Framework bootstrap3.
Dazu kommen dann noch die Erweiterung für den Datepicker und die Erweiterung für den Timepicker.
Natürlich habe ich noch etwas mit css nachgeholfen, das dient aber rein der Optik und die entsprechenden Dateien können über den html-test-Link abgerufen werden.
Wenn ich jetzt während dem laufenden Betrieb einen neuen Schaltvorgang anlege, wie bekommt mein python-Skript das mit?
Mit einem cron-job alle x Minuten die mysql-Datenbank zu überprüfen ist in meinen Augen keine Lösung.
Deshalb habe ich mich für eine Kommunikation über Websockets entschieden.
Dabei übernimmt das python-Skript die Rolle des Servers, und das html-Frontend ist der Client.
Ändert sich nun irgendwas an den Datensätzen, dann macht der Client eine Meldung an den Server.
Dieser wiederum kann nun entsprechend reagieren und den neu angelegten Schaltvorgang laden, oder einen aktuellen Vorgang stoppen.
Fangen wir mal mit dem wichtigsten Teil an, der Index-Seite mit der Übersicht über die vorhanden Schaltvorgänge:
timer_index.php
<? $page = isset($_GET['page']) ? $_GET['page'] : 1; $records_per_page = 10; $from_record_num = ($records_per_page * $page) - $records_per_page; include_once 'includes/config.php'; include_once 'includes/data.inc.php'; $database = new Config(); $db = $database->getConnection(); $product = new Data($db); $stmt = $product->readAll($page, $from_record_num, $records_per_page); $num = $stmt->rowCount(); ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="http://getbootstrap.com/favicon.ico"> <title>smartHome Zeitschaltuhr</title> <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Oswald:400,300,700' rel='stylesheet' type='text/css'> <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap-clockpicker.min.css" rel="stylesheet" type="text/css"> <link href="css/bootstrap-datepicker3.min.css" rel="stylesheet" type="text/css"> <link href="css/custom.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <!--Pseudo-Navigationsleiste mit Menu-Button und Anzeige des aktuellen Menu's--> <nav class="navbar-inverse navbar-fixed-top"> <a href="#menu-toggle" class="navbar-toggle" id="menu-toggle"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <div class="navbar-brand">Zeitschaltuhr</div> </nav> <!--Wrapper für die komplette Site--> <div id="wrapper"> <!--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"> <div class="row "> <div class="col-sm-12 col-widget"> <div class="first-widget widget same_height"> <div class="row"> <div class="col-xs-12 col-sm-offset-1 col-sm-5"> <h2>Schaltzeiten</h2> </div> </div> <hr> <?php if($num>0){ ?> <div class="row"> <div class="col-xs-12 col-sm-3 col-sm-offset-1 text-switch-center-left"> <a class="btn btn-primary btn-big-margin" href="timer_add.php" role="button"><span class='glyphicon glyphicon-plus' aria-hidden='true'></span> Neuer Schaltplan</a> </div> </div> <div class="row"> <div class="col-sm-10 col-sm-offset-1"> <div class="table-responsive"> <table class="table active timer-switch"> <thead> <tr> <th>Bezeichnung</th> <th class="hidden-xs">Typ</th> <th>Datum</th> <th>Start</th> <th>Stop</th> <th>Wiederholung</th> <th></th> </tr> </thead> <tbody> <?php while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){ extract($row); ?> <tr> <?php echo "<td>{$title}</td>" ?> <?php echo "<td class='hidden-xs'>{$device}</td>" ?> <?php $strDateStart = date("d.m.Y", strtotime($date_start_on)); $strTimeStart = date("H:i", strtotime($date_start_on)); $strTimeStop = date("H:i", strtotime($date_start_off)); if ($date_stop) { $strDateStop = date("d.m.Y", $date_stop_on); } echo "<td>{$strDateStart}</td>" ; ?> <?php echo "<td>{$strTimeStart}</td>" ?> <?php echo "<td>{$strTimeStop}</td>" ?> <?php echo "<td>{$duration}</td>" ?> <?php echo "<td width='100px'><a class='btn btn-warning btn-sm' href='timer_update.php?id={$scheduler_id}' role='button'><span class='glyphicon glyphicon-edit' aria-hidden='true'></span></a> <a class='btn btn-danger btn-sm' href='timer_delete.php?id={$scheduler_id}' role='button'><span class='glyphicon glyphicon-trash' aria-hidden='true'></span></a></td>" ?> </tr> <?php } ?> </tbody> </table> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-3 col-sm-offset-1 text-left hidden-xs"> <a class="btn btn-primary btn-big-margin" href="timer_add.php" role="button"><span class='glyphicon glyphicon-plus' aria-hidden='true'></span> Neuer Schaltplan</a> </div> <div class="col-xs-12 col-sm-4"> <?php $page_dom = "index.php"; include_once 'includes/pagination.inc.php'; } else{ ?> <div class="alert alert-warning alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Warning!</strong> ... </div> <?php } ?> </div> </div> </div> </div> <div class="col-xs-12 col-widget"> <div class="status-widget widget"> <div class="row"> <div class="col-xs-2 col-xs-offset-5 alert alert-success" id="socket-controller"> <p id="socket-controller-text">Zeitschaltuhr Server online</p> </div> </div> </div> </div> </div> </div> </div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="js/jquery.min.js"></script> <script src="js/bootstrap.min.js"></script> <script src="js/bootstrap-clockpicker.min.js"></script> <script src="js/bootstrap-datepicker.min.js"></script> <script src="js/validator.js"></script> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <script src="js/ie10-viewport-bug-workaround.js"></script> <script> var socketServer = new WebSocket('ws://192.168.127.30:5555'); socketServer.onerror = function(error) { document.getElementById('socket-controller-text').innerHTML = 'Zeitschaltuhr offline'; $("#socket-controller").removeClass('alert-success'); $("#socket-controller").addClass('alert-danger'); }; $("#menu-toggle").click(function(e) { e.preventDefault(); $("#wrapper").toggleClass("toggled"); }); </script> </body> </html>
Hier gibt es nichts besonderes.
Am Ende habe ich noch ein klein wenig Javascript. Damit frage ich nur ab ob das python-Programm läuft und zeige es an.
Die Lösung ist sicher nicht perfekt, mir ist aber nichts besseres eingefallen.
Vielleicht weiß jemand von euch einen besseren Weg?
Kommen wir zum Formular zum Neuanlegen von Schaltvorgängen.
timer_add.php
<?php include_once 'includes/config.php'; $database = new Config(); $db = $database->getConnection(); include_once 'includes/data.inc.php'; $product = new Data($db); ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="http://getbootstrap.com/favicon.ico"> <title>smartHome Zeitschaltuhr</title> <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Oswald:400,300,700' rel='stylesheet' type='text/css'> <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap-clockpicker.min.css" rel="stylesheet" type="text/css"> <link href="css/bootstrap-datepicker3.min.css" rel="stylesheet" type="text/css"> <link href="css/custom.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <script src="includes/timer_python_bridge.js"></script> </head> <body> <!--Pseudo-Navigationsleiste mit Menu-Button und Anzeige des aktuellen Menu's--> <nav class="navbar-inverse navbar-fixed-top"> <a href="#menu-toggle" class="navbar-toggle" id="menu-toggle"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <div class="navbar-brand">Zeitschaltuhr</div> </nav> <!--Wrapper für die komplette Site--> <div id="wrapper"> <!--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"> <div class="row"> <div class="col-sm-12 col-widget"> <div class="first-widget widget"> <div class="row"> <div class="col-xs-12 col-sm-offset-2 col-sm-5"> <h2>Neuen Schaltplan anlegen</h2> </div> </div> <hr> <?php if($_POST){ $product->title = $_POST['title']; $product->device = $_POST['device']; $product->strDateStart = $_POST['str_date_start']; $product->strTimeStart = $_POST['str_time_start']; $product->strTimeStop = $_POST['str_time_stop']; $product->strDateStop = $_POST['str_date_stop']; $product->duration = $_POST['duration']; $product->intervalNumber = $_POST['interval_number']; $product->intervalUnit = $_POST['interval_unit']; $product->weeklyMonday = $_POST['weekday_monday']; $product->weeklyTuesday = $_POST['weekday_tuesday']; $product->weeklyWednesday = $_POST['weekday_wednesday']; $product->weeklyThursday = $_POST['weekday_thursday']; $product->weeklyFriday = $_POST['weekday_friday']; $product->weeklySaturday = $_POST['weekday_saturday']; $product->weeklySunday = $_POST['weekday_sunday']; $tmp=$product->create(); if($tmp>0){ echo "<script type=\"text/javascript\">TimerNew($tmp);</script>"; ?> <!--<div class="alert alert-success alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Success!</strong><a href="timer_index.php">View Data</a>. </div>--> <?php }else{ ?> <div class="alert alert-danger alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Fail!</strong> </div> <?php } } ?> <form method="post"> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="title" class="control-label large">Name</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group"> <input type="text" class="form-control" size="25" value="" name="title" id="title" required> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 text-right"> <label for="device" class="control-label large">Typ</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <select class="form-control" name="device" id="device"> <option>Funkschalter</option> <option>USB-Steckdose</option> <option>TF Relais</option> </select> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="str_date_start" class="control-label">Datum</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group date"> <input type="text" id="str_date_start" class="form-control" name="str_date_start" required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 text-right"> <label for="str_time_start" class="control-label">von</label> </div> <div class="col-xs-6 col-sm-2 no_padding text-left"> <div class="input-group clockpicker"> <input type="text" class="form-control" id="str_time_start" name="str_time_start" required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span> </div> </div> <div class="clearfix visible-xs-block"></div> <div class="col-xs-4 col-sm-1 text-switch-right-center"> <label for="str_time_stop" class="control-label">bis</label> </div> <div class="col-xs-6 col-sm-2 no_padding text-left"> <div class="input-group clockpicker"> <input type="text" class="form-control" id="str_time_stop" name="str_time_stop" required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span> </div> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="str_date_stop" class="control-label">Ende</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group date"> <input type="text" id="str_date_stop" class="form-control" name="str_date_stop" readonly><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row"> <div class="col-xs-4 col-sm-2 text-right"> <label class="control-label">Dauer</label> </div> <div class="col-xs-7 col-xs-offset-1 col-sm-8 col-sm-offset-0 no_padding text-left"> <div class="form-group"> <label class="radio-inline"><input name="duration" checked id="duration" value="einmalig" type="radio" class="radio-big">einmalig Durchlauf</label> </div> <div class="form-group"> <label class="radio-inline"><input name="duration" id="duration" value="intervall" type="radio" class="radio-big" data-toggle="modal" data-target="#modalIntervall">Wiederholung in Intervallen</label> </div> <div class="form-group"> <label class="radio-inline"><input name="duration" id="duration" value="wochentag" type="radio" class="radio-big" data-toggle="modal" data-target="#modalWochentag">Wiederholung an Wochentagen</label> </div> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row"> <div class="col-xs-12 col-sm-6 col-sm-offset-6 text-switch-center-left no_padding"> <button type="submit" name="submit" class="btn btn-primary "><span class='glyphicon glyphicon-ok' aria-hidden='true'></span> Schaltplan anlegen</button> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row"> <div class="col-xs-12 col-sm-6 col-sm-offset-6 text-switch-center-left no_padding"> <a class="btn btn-default" href="timer_index.php" role="button"><span class='glyphicon glyphicon-remove' aria-hidden='true'></span> Abbrechen</a> </div> </div> </div> </div> <div class="modal" id="modalIntervall" tabindex="-1" role="dialog" aria-labelledby="modalIntervallLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="modalIntervallLabel">Intervall</h4> </div> <div class="modal-body"> <div class="row form-inline"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-right"> <label for="interval_number" class="control-label">wiederhole alle:</label> </div> <div class="col-xs-3 col-sm-2"> <div class="input-group"> <input type="number" id="interval_number" class="form-control" value="0" name="interval_number"> </div> </div> <div class="col-xs-4 col-sm-3 no_padding"> <select class="form-control" name="interval_unit" id="interval_unit"> <option>Minuten</option> <option>Stunden</option> <option>Tage</option> <option>Wochen</option> </select> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> </div> </div> </div> </div> <div class="modal" id="modalWochentag" tabindex="-1" role="dialog" aria-labelledby="modalWochentagLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="modalWochentagLabel">Intervall</h4> </div> <div class="modal-body"> <fieldset> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklyMonday" id="weeklyMonday"> Montag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklyTuesday" id="weeklyTuesday"> Dienstag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklyWednesday" id="weeklyWednesday"> Mittwoch</label> </div> </div> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklyThursday" id="weeklyThursday"> Donnerstag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklyFriday" id="weeklyFriday"> Freitag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklySaturday" id="weeklySaturday"> Samstag</label> </div> </div> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weeklySunday" id="weeklySunday"> Sonntag</label> </div> </div> <br> </fieldset> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> </div> </div> </div> </div> </form> </div> </div> <div class="col-xs-12 col-widget"> <div class="status-widget widget"> <div class="row"> <div class="col-xs-2 col-xs-offset-5 alert alert-success" id="socket-controller"> <p id="socket-controller-text">Zeitschaltuhr Server online</p> </div> </div> </div> </div> </div> </div> </div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="js/jquery.min.js"></script> <script src="js/bootstrap.min.js"></script> <script src="js/bootstrap-clockpicker.min.js"></script> <script src="js/bootstrap-datepicker.min.js"></script> <script src="js/validator.js"></script> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <script src="js/ie10-viewport-bug-workaround.js"></script> <script> $("#menu-toggle").click(function(e) { e.preventDefault(); $("#wrapper").toggleClass("toggled"); }); $('.clockpicker').clockpicker({ donetext: 'Fertig', 'default': 'now' }); $('.input-group.date').datepicker({ format: "dd.mm.yyyy", language: "de", orientation: "top auto", autoclose: true }); </script> </body> </html>
Hier findet das erste mal eine Kommunikation über websockets statt.
In der Zeile:
$tmp=$product->create(); if($tmp>0){ echo "<script type=\"text/javascript\">TimerNew($tmp);</script>";
bekomme ich die ID des neuen Schaltplan geliefert und rufe dann die entsprechende Javascript-Funktion auf.
Diese findet man hier:
timer_python_bridge.js
// JavaScript Document var socketServer = new WebSocket('ws://192.168.1.1:5555'); var offline = false; socketServer.onerror = function(error) { document.getElementById('socket-controller-text').innerHTML = 'Zeitschaltuhr offline'; $("#socket-controller").removeClass('alert-success'); $("#socket-controller").addClass('alert-danger'); offline = true; console.log('WebSocket Error : ' + error); }; socketServer.onopen = function(event) {}; socketServer.onmessage = function(event) {}; socketServer.onclose = function(event) {}; function TimerNew (timer) { var send_string = JSON.stringify(["new",timer]) this.send(send_string); }; function TimerUpdate (timer) { var send_string = JSON.stringify(["update",timer]) this.send(send_string); }; function TimerDelete (timer) { var send_string = JSON.stringify(["delete",timer]) this.send(send_string); }; this.send = function (message, callback) { if (offline=false) { this.waitForConnection(function () { socketServer.send(message); window.location = "timer_index.php"; if (typeof callback !== 'undefined') { callback(); } }, 1000); } else { window.location = "timer_index.php"; } }; this.waitForConnection = function (callback, interval) { if (socketServer.readyState === 1) { callback(); } else { var that = this; // optional: implement backoff for interval here setTimeout(function () { that.waitForConnection(callback, interval); }, interval); } };
Der Javascript-Code überprüft zuerst ob der python-Server online ist, falls nicht wird nach einer erfolgreichen Aktion (Neu, Ändern oder Löschen) direkt wieder zur index-Seite gesprungen.
Ist der Server aber erreichbar, dann wird zuerst ein entsprechendes Kommando an den python-Server geschickt und erst dann springe ich wieder zur Hauptseite.
Auch hier gibt es bestimmt noch Potenzial für Verbesserungen.
Okay, als nächstes kommt die Seite für das Ändern von vorhandenen Datensätzen.
Normalerweise müsste es möglich sein diese Seite irgendwie mit der Seite zum Neuanlegen zu verschmelzen.
Im Augenblick muss ich deshalb den kompletten Code für das Formular doppelt schreiben – kann nicht Sinn der Sache sein.
Mal schauen ob ich das noch ändern kann.
timer_update.php
<?php include_once 'includes/config.php'; $id = isset($_GET['id']) ? $_GET['id'] : die('ERROR: missing ID.'); $database = new Config(); $db = $database->getConnection(); include_once 'includes/data.inc.php'; $product = new Data($db); $product->id = $id; $product->readOne(); ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="http://getbootstrap.com/favicon.ico"> <title>smartHome Zeitschaltuhr</title> <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700,800' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Oswald:400,300,700' rel='stylesheet' type='text/css'> <link href="http://getbootstrap.com/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap-clockpicker.min.css" rel="stylesheet" type="text/css"> <link href="css/bootstrap-datepicker3.min.css" rel="stylesheet" type="text/css"> <link href="css/custom.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <script src="includes/timer_python_bridge.js"></script> </head> <body> <!--Pseudo-Navigationsleiste mit Menu-Button und Anzeige des aktuellen Menu's--> <nav class="navbar-inverse navbar-fixed-top"> <a href="#menu-toggle" class="navbar-toggle" id="menu-toggle"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <div class="navbar-brand">Zeitschaltuhr</div> </nav> <!--Wrapper für die komplette Site--> <div id="wrapper"> <!--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"> <div class="row"> <div class="col-sm-12 col-widget"> <div class="first-widget widget"> <div class="row"> <div class="col-xs-12 col-sm-offset-2 col-sm-5"> <h2>Schaltplan ändern</h2> </div> </div> <hr> <?php if($_POST){ $product->title = $_POST['title']; $product->device = $_POST['device']; $product->strDateStart = $_POST['str_date_start']; $product->strTimeStart = $_POST['str_time_start']; $product->strTimeStop = $_POST['str_time_stop']; $product->strDateStop = $_POST['str_date_stop']; $product->duration = $_POST['duration']; $product->intervalNumber = $_POST['interval_number']; $product->intervalUnit = $_POST['interval_unit']; $product->weeklyMonday = $_POST['weekly_monday']; $product->weeklyTuesday = $_POST['weekly_tuesday']; $product->weeklyWednesday = $_POST['weekly_wednesday']; $product->weeklyThursday = $_POST['weekly_thursday']; $product->weeklyFriday = $_POST['weekly_friday']; $product->weeklySaturday = $_POST['weekly_saturday']; $product->weeklySunday = $_POST['weekly_sunday']; if($product->update()){ echo "<script type=\"text/javascript\">TimerUpdate($product->id);</script>"; ?> <!--<script>window.location.href='timer_index.php'</script>--> <?php }else{ ?> <div class="alert alert-danger alert-dismissible" role="alert"> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button> <strong>Fail!</strong> </div> <?php } } ?> <form method="post"> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="title" class="control-label large">Name</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group"> <input type="text" class="form-control" size="25" name="title" id="title" value='<?php echo $product->title; ?>' required> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 text-right"> <label for="device" class="control-label large">Typ</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <select class="form-control" name="device" id="device"> <option <?php if ($product->device=='Funkschalter') echo "selected"?>>Funkschalter</option> <option <?php if ($product->device=='USB-Steckdose') echo "selected"?>>USB-Steckdose</option> <option <?php if ($product->device=='TF Relais') echo "selected"?>>TF Relais</option> </select> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="str_date_start" class="control-label">Datum</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group date"> <input type="text" id="str_date_start" class="form-control" name="str_date_start" value='<?php echo $product->strDateStart; ?>' required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 text-right"> <label for="str_time_start" class="control-label">von</label> </div> <div class="col-xs-6 col-sm-2 no_padding text-left"> <div class="input-group clockpicker"> <input type="text" class="form-control" id="str_time_start" name="str_time_start" value='<?php echo $product->strTimeStart; ?>' required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span> </div> </div> <div class="clearfix visible-xs-block"></div> <div class="col-xs-4 col-sm-1 text-switch-right-center"> <label for="str_time_stop" class="control-label">bis</label> </div> <div class="col-xs-6 col-sm-2 no_padding text-left"> <div class="input-group clockpicker"> <input type="text" class="form-control" id="str_time_stop" name="str_time_stop" value='<?php echo $product->strTimeStop; ?>' required readonly><span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span> </div> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row form-inline"> <div class="col-xs-4 col-sm-2 col-sm-offset-4 text-right"> <label for="str_date_stop" class="control-label">Ende</label> </div> <div class="col-xs-6 col-sm-4 no_padding text-left"> <div class="input-group date"> <input type="text" id="str_date_stop" class="form-control" name="str_date_stop" value='<?php if ($product->dateStop) echo $product->strDateStop; ?>' readonly><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span> </div> </div> </div> </div> <div class="col-xs-12 col-sm-7"> <div class="row"> <div class="col-xs-4 col-sm-2 text-right"> <label class="control-label">Dauer</label> </div> <div class="col-xs-7 col-xs-offset-1 col-sm-8 col-sm-offset-0 no_padding text-left"> <div class="form-group"> <label class="radio-inline"><input name="duration" id="duration" value="einmalig" type="radio" class="radio-big" <?php if ($product->duration=='einmalig') echo "checked" ?>>einmalig Durchlauf</label> </div> <div class="form-group"> <label class="radio-inline"><input name="duration" id="duration" value="intervall" type="radio" class="radio-big" data-toggle="modal" data-target="#modalIntervall" <?php if ($product->duration=='intervall') echo "checked" ?>>Wiederholung in Intervallen</label> </div> <div class="form-group"> <label class="radio-inline"><input name="duration" id="duration" value="wochentag" type="radio" class="radio-big" data-toggle="modal" data-target="#modalWochentag" <?php if ($product->duration=='wochentag') echo "checked" ?>>Wiederholung an Wochentagen</label> </div> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row"> <div class="col-xs-12 col-sm-6 col-sm-offset-6 text-switch-center-left no_padding"> <button type="submit" name="submit" class="btn btn-primary "><span class='glyphicon glyphicon-ok' aria-hidden='true'></span> Änderungen speichern</button> </div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-sm-5"> <div class="row"> <div class="col-xs-12 col-sm-6 col-sm-offset-6 text-switch-center-left no_padding"> <a class="btn btn-default" href="timer_index.php" role="button"><span class='glyphicon glyphicon-remove' aria-hidden='true'></span> Abbrechen</a> </div> </div> </div> </div> <div class="modal" id="modalIntervall" tabindex="-1" role="dialog" aria-labelledby="modalIntervallLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="modalIntervallLabel">Intervall</h4> </div> <div class="modal-body"> <div class="row form-inline"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-right"> <label for="interval_number" class="control-label">wiederhole alle:</label> </div> <div class="col-xs-3 col-sm-2"> <div class="input-group"> <input type="number" id="interval_number" class="form-control" name="interval_number" value='<?php echo $product->intervalNumber; ?>'> </div> </div> <div class="col-xs-4 col-sm-3 no_padding"> <select class="form-control" name="interval_unit" id="interval_unit" value='<?php echo $product->intervalUnit; ?>'> <option <?php if ($product->intervalUnit=='Minuten') echo "selected"?>>Minuten</option> <option <?php if ($product->intervalUnit=='Stunden') echo "selected"?>>Stunden</option> <option <?php if ($product->intervalUnit=='Tage') echo "selected"?>>Tage</option> <option <?php if ($product->intervalUnit=='Wochen') echo "selected"?>>Wochen</option> </select> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> </div> </div> </div> </div> <div class="modal" id="modalWochentag" tabindex="-1" role="dialog" aria-labelledby="modalWochentagLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="modalWochentagLabel">Intervall</h4> </div> <div class="modal-body"> <fieldset> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_monday" id="weekly_monday" <?php if ($product->weeklyMonday) echo "checked" ?>> Montag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_tuesday" id="weekly_tuesday" <?php if ($product->weeklyTuesday) echo "checked" ?>> Dienstag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_wednesday" id="weekly_wednesday" <?php if ($product->weeklyWednesday) echo "checked" ?>> Mittwoch</label> </div> </div> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_thursday" id="weekly_thursday" <?php if ($product->weeklyThursday) echo "checked" ?>> Donnerstag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_friday" id="weekly_friday" <?php if ($product->weeklyFriday) echo "checked" ?>> Freitag</label> </div> <div class="col-xs-4 col-sm-3 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_saturday" id="weekly_saturday" <?php if ($product->weeklySaturday) echo "checked" ?>> Samstag</label> </div> </div> <div class="row"> <div class="col-xs-4 col-sm-3 col-sm-offset-1 text-left"> <label class="control-label"><input type="checkbox" value="true" name="weekly_sunday" id="weekly_sunday" <?php if ($product->weeklySunday) echo "checked" ?>> Sonntag</label> </div> </div> <br> </fieldset> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal">OK</button> </div> </div> </div> </div> </form> </div> </div> <div class="col-xs-12 col-widget"> <div class="status-widget widget"> <div class="row"> <div class="col-xs-2 col-xs-offset-5 alert alert-success" id="socket-controller"> <p id="socket-controller-text">Zeitschaltuhr Server online</p> </div> </div> </div> </div> </div> </div> </div> <!-- Bootstrap core JavaScript ================================================== --> <!-- Placed at the end of the document so the pages load faster --> <script src="js/jquery.min.js"></script> <script src="js/bootstrap.min.js"></script> <script src="js/bootstrap-clockpicker.min.js"></script> <script src="js/bootstrap-datepicker.min.js"></script> <script src="js/validator.js"></script> <!-- IE10 viewport hack for Surface/desktop Windows 8 bug --> <script src="js/ie10-viewport-bug-workaround.js"></script> <script> $( document ).ready(function() { var heights = $(".eq-height").map(function() { return $(this).height(); }).get(), maxHeight = Math.max.apply(null, heights); $(".eq-height").height(maxHeight); }); $("#menu-toggle").click(function(e) { e.preventDefault(); $("#wrapper").toggleClass("toggled"); }); $('.clockpicker').clockpicker({ donetext: 'Fertig', 'default': 'now' }); $('.input-group.date').datepicker({ format: "dd.mm.yyyy", language: "de", orientation: "top auto", autoclose: true }); </script> </body> </html>
Auch hier nutze ich den Javascript-Code zum Senden der Kommandos.
Außer das die Felder vorbelegt sind gibt es hier eigentlich keine nennenswerte Unterschiede.
Als letztes kommt jetzt noch der Code zum Löschen von Datensätzen.
Hier gibt es nun wirklich nichts Besonderes.
timer_delete.php
<script src="includes/timer_python_bridge.js"></script> <?php include_once 'includes/config.php'; include_once 'includes/data.inc.php'; $database = new Config(); $db = $database->getConnection(); $product = new Data($db); $tmp = $_GET['id']; $product->id = isset($_GET['id']) ? $_GET['id'] : die('ERROR: missing ID.'); if($product->delete()){ echo "<script type=\"text/javascript\">TimerDelete($tmp);</script>"; } else{ echo "<script>alert('Fail')</script>"; } ?>
Damit ist das Frontend fertig, die css Dateien sind eigentlich selbst erklärend.
Wie schon zu Beginn angesprochen ist das Frontend nicht fehlerfrei und weit weg davon perfekt zu sein.
Derzeit fallen mir 2-3 Probleme ein, die ich am Ende des Artikel besprechen werden.
Wie sind jetzt also soweit das wir Schaltvorgänge anlegen, ändern und löschen können.
Außerdem sendet das Frontend Status-Meldungen zu dem python-Skript.
Kommen wir jetzt also zum „Herz“ der digitalen Zeitschaltuhr.
Gleich vorweg will ich „Sirius3“ und „BlackJack“ aus dem deutschen python-Forum danken, ohne die beiden wäre die python-Files nicht so sauber und effizient.
Also, mein python-Skript arbeitet mit diversen Erweiterungen, diese kann man einfach mit „pip“ installieren.
pip3.4 install websockets pip3.4 install pymysql pip3.4 install apscheduler
Das Skript selbst arbeitet dabei nach einem einfachen Prinzip.
Beim Programmstart wird zuerst die mySQL-Datenbank ausgelesen und die Jobs entsprechend gestartet.
Danach wird der websocket-Server gestartet und auf Status-Updates gelauscht.
Zuerst das Hauptprogramm
scheduler.py
import asyncio from asyncio.queues import Queue import logging import json from datetime import datetime import pymysql import websockets from timerswitch import TimerSwitch machine_ip = "192.168.127.30" machine_port = 5555 consumers_gui = [] def send_to_usb(title, scheduler_id, switch_to, date_stop): logger.info( 'Timerswitch ... USB ID = %s switch to = %s date_stop = %s' % (title, switch_to, date_stop)) check_date_stop(scheduler_id, switch_to, date_stop) def send_to_radio(title, scheduler_id, switch_to, date_stop): logger.info('Timerswitch ... Funk ID = %s switch to = %s loop = %s' % (title, switch_to, date_stop)) check_date_stop(scheduler_id, switch_to, date_stop) def send_to_relais_tf(title, scheduler_id, switch_to, date_stop): logger.info('Timerswitch ... TF Relais ID = %s switch to = %s loop = %s' % (title, switch_to, date_stop)) check_date_stop(scheduler_id, switch_to, date_stop) def check_date_stop(scheduler_id, switch_to, date_stop): if (not switch_to) and (date_stop is not None): if datetime.now() >= date_stop: timer.delete_db(scheduler_id) @asyncio.coroutine def sending_loop_gui(websocket): # create sending-queue loop = asyncio.get_event_loop() sending_queue_gui = Queue() logger.info('websockets .... GUI Queue startet') def changed(tmp): loop.call_soon_threadsafe(sending_queue_gui.put_nowait, tmp) try: consumers_gui.append(changed) logger.info('websockets .... ein GUI-Client wurde in die Queue aufgenommen') while True: data = yield from sending_queue_gui.get() yield from websocket.send(data) logger.debug('websockets .... Sende json Daten -> GUI : %s' % data) finally: consumers_gui.remove(changed) logger.info('websockets .... ein GUI-Client wurde aus der Queue entfernt') @asyncio.coroutine def socket_handler_gui(websocket, path): # set up sending-queue task = asyncio.async(sending_loop_gui(websocket)) while True: message = yield from websocket.recv() if message is None: break logger.debug('websockets .... Empfange json Daten von GUI-Client : %s' % message) gui_message_handler(message) task.cancel() def gui_message_handler(message): # decode JSON String message = json.loads(message) # extract variables from json command = message[0] scheduler_id = message[1] logger.debug( 'websockets .... GUI -> Nachricht: %s ID = %s' % (command, scheduler_id)) if command == "new": timer.load(scheduler_id) elif command == "update": timer.reload(scheduler_id) elif command == "delete": timer.delete_job(scheduler_id) def set_logging(): console_handler = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S') console_handler.setFormatter(formatter) logger.addHandler(console_handler) if __name__ == '__main__': # # set up Logging Deamon # logger = logging.getLogger('Timerswitch') logger.setLevel(logging.DEBUG) set_logging() logger.info('Timerswitch ... startet') # # set up MySQL Connection # mysql_connection = pymysql.connect(host="192.168.1.1", port=3306, user="db_user", passwd="db_passwort", db="db_name", autocommit=True) mysql_cursor = mysql_connection.cursor(pymysql.cursors.DictCursor) logger.info('mySQL ......... Verbindung online') # # set up TimerSwitch # methods = {'USB-Steckdose': send_to_usb, 'Funkschalter': send_to_radio, 'TF Relais': send_to_relais_tf} timer = TimerSwitch(logger, mysql_cursor, methods) timer.start() # # set up WebSocktes # gui_server = websockets.serve(socket_handler_gui, machine_ip, machine_port) asyncio.get_event_loop().run_until_complete(gui_server) logger.info('websockets .... System online') asyncio.get_event_loop().run_forever()
Zu Beginn importiere ich wie gewohnt verschiedene Module, unter anderem das Modul „TimerSwitch“ – in diese Klasse habe ich einen Großteil der Logik ausgelagert.
Danach folgenden die verschiedenen „Schalt“-Funktionen.
Genau hier würden dann die verschiedenen Geräte geschaltet (Praxisbeispiele folgen in Teil 3).
Hier müsste man das Programm dann auch für die eigenen Wünsche ändern/erweitern.
Die „check_date_stop„-Funktion überwacht ob ein „End-Datum“ eingegeben und eventuell erreicht wurde.
Dafür wird einfach das aktuelle Datum mit dem vorgegebenen Stop-Datum verglichen.
Wenn ja dann wird in der Datenbank der entsprechende Eintrag entfernt.
Die nächsten 3 x Funktionen sorgen für die Kommunikation über die websocket-Schnittstelle.
In „gui_message_handler“ werden die einkommenden Statusmeldungen decodiert und je nach Nachricht eine entsprechende Aktion ausgelöst.
Die logging-Funktion erklärt sich von selbst.
Danach folgt dann nur noch das Startprogramm.
Logging starten, dann mit mySQL verbinden und die „Schalt“-Funktionen definieren.
Dann die TimerSwitch-Klasse laden und direkt starten.
Zu guter letzt wird dann der websocket-Server gestartet.
Das war es.
Bleibt nur noch die TimerSwitch-Klasse, die eigentliche Zeitschaltuhr.
timerswitch.py
from apscheduler.schedulers.asyncio import AsyncIOScheduler Beim class TimerSwitch: UNIT2KEYWORD = { 'Minuten': 'minutes', 'Stunden': 'hours', 'Tage': 'days', 'Wochen': 'weeks' } WEEK2WEEK = [ ('mon', 'weekly_monday'), ('tue', 'weekly_tuesday'), ('wed', 'weekly_wednesday'), ('thu', 'weekly_thursday'), ('fri', 'weekly_friday'), ('sat', 'weekly_saturday'), ('sun', 'weekly_sunday'), ] def __init__(self, logging_daemon, db_connection, methods): self._scheduler = AsyncIOScheduler() self._methods = methods self._logging_daemon = logging_daemon self._db_connection = db_connection self._logging_daemon.info('TimerSwitch ... initialisiert') def load(self, scheduler_id=None): sql = "SELECT scheduler_id, title, device, date_start_on, date_start_off, date_stop, date_stop_on, " \ "date_stop_off, duration, interval_number, interval_unit, weekly_monday, weekly_tuesday, " \ "weekly_wednesday, weekly_thursday, weekly_friday, weekly_saturday, weekly_sunday FROM schedulers" if isinstance(scheduler_id, int): sql += " WHERE scheduler_id = %s" self._db_connection.execute(sql, scheduler_id) else: self._db_connection.execute(sql) results = self._db_connection.fetchall() for result in results: self._add(result) def _add(self, dataset): scheduler_id = dataset['scheduler_id'] title = dataset['title'] date_start_on = dataset['date_start_on'] date_start_off = dataset['date_start_off'] date_stop_on = dataset['date_stop_on'] date_stop_off = dataset['date_stop_off'] duration = dataset['duration'] method = self._methods[dataset['device']] week = ','.join( abr for abr, full in self.WEEK2WEEK if dataset[full] ) if duration == 'einmalig': scheduler_type = 'date' args_on = dict(run_date=date_start_on) args_off = dict(run_date=date_start_off) elif duration == 'intervall': scheduler_type = 'interval' interval_argument = {self.UNIT2KEYWORD[dataset['interval_unit']]: dataset['interval_number']} args_on = dict(interval_argument, start_date=date_start_on, end_date=date_stop_on) args_off = dict(interval_argument, start_date=date_start_off, end_date=date_stop_off) elif duration == 'wochentag': scheduler_type = 'cron' args_on = dict( day_of_week=week, hour=date_start_on.hour, minute=date_start_on.minute, start_date=date_start_on, end_date=date_stop_on) args_off = dict( day_of_week=week, hour=date_start_off.hour, minute=date_start_off.minute, start_date=date_start_off, end_date=date_stop_off) self._scheduler.add_job(method, scheduler_type, args=[title, scheduler_id, True, None], id='%son' % scheduler_id, **args_on) self._scheduler.add_job(method, scheduler_type, args=[title, scheduler_id, False, date_stop_off], id='%soff' % scheduler_id, **args_off) self._logging_daemon.info('Timerswitch ... ADD %s id = %s' % (title, scheduler_id)) self._logging_daemon.debug( 'Timerswitch ... self._scheduler.add_job(%s, %s, args=[%s, %s, True, None], id=%soff, %s' % ( method, scheduler_type, title, scheduler_id, scheduler_id, args_on)) def reload(self, scheduler_id): self.delete_job(scheduler_id) self.load(scheduler_id) self._logging_daemon.info('Timerswitch ... Reload ID = %s' % scheduler_id) def delete_job(self, scheduler_id): self._scheduler.remove_job(str(scheduler_id) + 'on') self._scheduler.remove_job(str(scheduler_id) + 'off') self._logging_daemon.info('Timerswitch ... Delete Job ID = %s' % scheduler_id) def delete_db(self, scheduler_id): if isinstance(scheduler_id, int): scheduler_id = int(scheduler_id) self._db_connection.execute("DELETE FROM schedulers WHERE scheduler_id = %s", scheduler_id) self._logging_daemon.info('Timerswitch ... Delete DB ID = %s' % scheduler_id) def start(self): self.load() self._scheduler.start()
Diese Klasse hat es, zumindest für meine Verhältnisse, in sich.
Bei den ganzen Optimierungen haben mir die Leute aus dem python-Forum sehr viel geholfen und den Code damit gewaltig verkürzt.
Zuerst legen wir uns eine Hilfs-Dictionary für den Intervall-Typ und eine Hilfs-Liste für die Wochentag-Auswahl an.
Wofür sieht man später, soviel vorweg, wir ersparen uns damit sehr viel doppelten Code.
Beim Initialisieren passiert nicht viel mehr als das die ich die Übergabevariabeln übernehme.
Kommen wir zur „load“ – der Name ist Programm.
Wurde eine Datensatz-ID übergeben, so wird nach dieser gesucht – ansonsten werden alle Datensätze zurückgegeben.
Dabei wird für jeden Datzsatz die „_add„-Funktion aufgerufen – diese trägt den Job in den apscheduler ein.
Bei der „_add“-Funktion habe ich die meiste Hilfe bekommen, zu 100% verstehe ich die Verkürzungen immer noch nicht.
Zuerst speichere ich die grundsätzlichen Daten aus dem Datensatz in Variablen ab, soweit so klar.
Danach wähle ich die gewünschte Schalt-Funktion aus
method = self._methods[dataset['device']]
Jetzt folgt eine Zeile, welche die eventuell ausgewählten Wochentage in eine für den apscheduler verständliche Form bringt.
week = ','.join( abr for abr, full in self.WEEK2WEEK if dataset[full] )
So, nun geht es mit dem Intervall-Typ weiter.
Entsprechend dem Typ wird die scheduler-Variante gesetzt (date, intervall oder cron).
Mehr zu den unterschiedlichen Varianten findet Ihr hier:
Jetzt werden neben der scheduler-Variante noch andere Argumente vorbereitet.
Bsp: „cron“ braucht eine Angabe der Wochentage, „interval“ Angaben über den Intervall-Typ und „date“ nur Start-/Stop-Angaben.
Nun müssen wir nur noch die 2 Job’s eintragen.
Einmal mit einem ON- und einmal mit einem OFF-Befehl.
Die Funktion „reload“ dient dazu ein im Frontend geänderten Schaltvorgang neu zu starten und ist weitestgehend selbsterklärend.
Das gleiche gilt für die Funktionen „delete_job“ und „delete_db“ – beide erklären sich selbst.
Die letzte Funktion „start“ sorgt zu Beginn wie der Name schon sorgt für den Start.
Dazu werden alle vorhanden Daten (deshalb keine Übergabe einer ID) geladen und dann der scheduler gestartet.
Und damit sind wir am Ende des Sourcecodes.
Ich bin nicht wirklich gut im Schreiben und Erklären, deswegen dauert es auch immer so lange.
Ich hoffe ich konnte es trotzdem etwas verständlich machen.
Sollten noch Fragen sein, dann benutzt bitte die Kommentar-Funktion – ich werde dann versuchen euch so gut wie möglich zu helfen.
Die Software kann zusammen auf einem Raspberry Pi laufen, aber auch verteil auf 2 x PC’s.
Bei mir läuft das Frontend auf dem stärkeren Wohnungs-Server und das python-Skript auf einem Raspberry Pi.
So, kommen wir zu den schon angesprochenen Problemen bei denen ich nicht weiter komme und auf eure Hilfe hoffe:
? Menü ein-/ausklappen im HTML-Frontend ?
Wen ihr das Frontend auf einem normalen PC-Monitor anschaut fällt der Fehler am meisten auf.
Dafür einfach das Menü mit dem Button links-oben mal ein-/ausklappen.
Wie Ihr sehen könnt verschiebt sich die Box nicht richtig nach rechts, sondern wird irgendwie gequetscht.
Vor allem beim Einklappen des Menüs führt das zu sehr unschönen Effekten.
Ich habe jetzt schon viele Stunden versucht das irgendwie hinzubekommen, aber nichts.
Ich steige nicht genug in jquery, bootstrap und css durch um den Fehler zu finden.
Kann mir jemand da helfen?
? Überprüfung ob websocket-Server läuft ?
Wie schon erwähnt finde ich meine Methode zum überprüfen ob der socket-Server am laufen ist nicht gut.
Zum einen reagiert sie langsam und zum anderen teste ich ja nicht ob der Server offline ist, sondern nur ob generell ein Fehler aufgetreten ist (gleich welcher Art).
Da muss es doch sicherlich eine besser Lösung geben?
Vorschau …
Wie geht es weiter?
Als nächstes steht der abschließende Teil 3 auf dem Programm.
Dabei werde ich den CSS-Code aufräumen, das Frontend um eine 2. Tabelle mit Schaltvorgängen erweitern und am Ende den python-Code um praktische Anwendungen erweitern.
Dabei werde ich Funksteckdosen und Hardware-Relais über Tinkerforge-Hardware steuern, dazu eine USB-Steckdosenleiste aus Fernost und eine per GPIO am Raspberry angeschlossen LED schalten.
Sprich es wird von allem etwas dabei sein.
Jetzt verabschiede ich mich mit der Hoffnung euch beim nächsten Teil wieder zu sehen und dieses mal vielleicht ein paar Kommentare zu bekommen.
Du musst dringend den Inhalt des Felds Title escapen sonst schmeißt da jeder Depp HTML/JS rein.
Wo genau das jetzt passieren muss kannst du entscheiden. Wichtig ist jedoch das NICHT im Frontend mit JS zu tun.
Merke: NIEMALS dem User trauen. Alles was von dem kommt ist potentiell böse. Das escapen muss also wohl im PHP Backend zum speichern passieren 😉
Ich habe mich für den 3. Teil des Artikels nochmal mit dem Thema Sicherheit beschäftigt.
Der Sourcecode wurde von mir entsprechend verändert, ich hoffe damit alle Lücken geschlossen zu haben.
Meine Lösung wird dann im nächsten Eintrag (am Wochenende) präsentiert.
Bitte schaut dann nochmal drüber und sagt mir ob das ausreicht.