Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a99fdc45c | ||
|
|
949649c9a9 |
No files matched your search
@@ -0,0 +1,127 @@
|
|||||||
|
%%
|
||||||
|
%% LaTeX-Vorlage fuer Projektarbeit
|
||||||
|
%%
|
||||||
|
|
||||||
|
|
||||||
|
%% Neue User-Befehle
|
||||||
|
%%
|
||||||
|
%% \title Titel der Projektarbeit
|
||||||
|
%% \semester z.B. Wintersemester 2008/2009
|
||||||
|
%% \betreuer Kommagetrennte Liste der Betreuer
|
||||||
|
%% \maketitle Erzeugen der Titelseite
|
||||||
|
%% \keywords Schlüsselwörter angeben
|
||||||
|
|
||||||
|
\NeedsTeXFormat{LaTeX2e}
|
||||||
|
\ProvidesPackage{arbeit}[2014/10/09 Paket fuer Projektarbeiten]
|
||||||
|
\RequirePackage{scrpage2}
|
||||||
|
\RequirePackage{hyperref} % Hyperref möglichst spät laden. Vielleicht hier?
|
||||||
|
|
||||||
|
% Kopf- und Fußzeilen setzen
|
||||||
|
\clearscrheadfoot
|
||||||
|
\pagestyle{scrheadings}
|
||||||
|
\cfoot{\thepage}
|
||||||
|
|
||||||
|
%% Schriftalternativen zu dem Latex-Standard: Computer Modern (CM)
|
||||||
|
%%----------------------------------------------------------------
|
||||||
|
%% Englaufende Times/Helvetica/Courier laden, aber mit richtigen Skalierungen (siehe l2tabu)
|
||||||
|
%\usepackage{mathptmx}
|
||||||
|
%\usepackage[scaled=.90]{helvet}
|
||||||
|
%\usepackage{courier} % kann auch weggelassen werden
|
||||||
|
%% Palatino, genauso wenig englaufend wie CM, aber riesiger Zeilenabstand
|
||||||
|
%\usepackage{mathpazo}
|
||||||
|
%\usepackage[scaled=.95]{helvet}
|
||||||
|
%\usepackage{courier}
|
||||||
|
|
||||||
|
%% Neue User-Befehle
|
||||||
|
%%------------------
|
||||||
|
\newcommand{\keywordsline}{\paragraph{Keywords:} \@keywords}
|
||||||
|
\newcommand{\@betreuer}{}
|
||||||
|
\newcommand{\betreuer}[1]{\renewcommand{\@betreuer}{#1}}
|
||||||
|
\newcommand{\@semester}{}
|
||||||
|
\newcommand{\semester}[1]{\renewcommand{\@semester}{#1}}
|
||||||
|
\newcommand{\@keywords}{}
|
||||||
|
\newcommand{\keywords}[1]{\renewcommand{\@keywords}{#1}}
|
||||||
|
|
||||||
|
\AtBeginDocument{
|
||||||
|
\hypersetup{bookmarksnumbered=true,
|
||||||
|
pdfcreator={Adobe Acrobat Distiller},
|
||||||
|
pdfproducer={LaTex with hyperref}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
% Eigentliche Ausgabe der Titelseite
|
||||||
|
\renewcommand{\@maketitle}
|
||||||
|
{%
|
||||||
|
\hypersetup{%
|
||||||
|
pdftitle={\@title},
|
||||||
|
pdfsubject={Projektarbeit \@semester},
|
||||||
|
pdfauthor={\@author},
|
||||||
|
pdfkeywords={\@keywords},
|
||||||
|
}%
|
||||||
|
\begin{center}
|
||||||
|
\vskip 2em%
|
||||||
|
{\titlefont\huge \@title \par}%
|
||||||
|
\vskip 2em%
|
||||||
|
{\large\textbf{Projektarbeit}\par}%
|
||||||
|
{\large \@author\par}%
|
||||||
|
\vskip 2em%
|
||||||
|
{\large Fakultät Elektrotechnik Feinwerktechnik Informationstechnik, \\
|
||||||
|
Technische Hochschule Nürnberg Georg Simon Ohm,\\
|
||||||
|
Wassertorstraße 10,\\
|
||||||
|
90489 Nürnberg, Germany\\
|
||||||
|
Betreuer: \@betreuer\par}%
|
||||||
|
\vspace*{3em} %
|
||||||
|
\end{center}%
|
||||||
|
}
|
||||||
|
|
||||||
|
% Aus scrartcl.cls extrahiert, sorgt fuer richtiges Setzen bei ein-/zweispaltigem Satz
|
||||||
|
\renewcommand{\maketitle}{
|
||||||
|
\if@twocolumn
|
||||||
|
\ifnum \col@number=\@ne
|
||||||
|
\@maketitle
|
||||||
|
\else
|
||||||
|
\twocolumn[\@maketitle]%
|
||||||
|
\fi
|
||||||
|
\else
|
||||||
|
\newpage
|
||||||
|
\global\@topnum\z@
|
||||||
|
\@maketitle
|
||||||
|
\fi
|
||||||
|
}
|
||||||
|
|
||||||
|
% Aus scrartcl.cls genommen und angepasst -> Kursive Schrift
|
||||||
|
\renewenvironment{abstract}{%
|
||||||
|
\if@twocolumn\if@abstrt %Zweispaltig
|
||||||
|
\addsec*{\abstractname}
|
||||||
|
\it% Abstract kursiv setzen
|
||||||
|
\fi
|
||||||
|
\it% Abstract kursiv setzen
|
||||||
|
\else% Einspaltig
|
||||||
|
\if@abstrt
|
||||||
|
\small
|
||||||
|
\begin{center}
|
||||||
|
{\normalfont\sectfont\nobreak\abstractname
|
||||||
|
\vspace{-.5em}\vspace{\z@}}%
|
||||||
|
\end{center}
|
||||||
|
\fi
|
||||||
|
\quotation
|
||||||
|
\fi
|
||||||
|
}{%
|
||||||
|
\if@twocolumn\else\endquotation\fi
|
||||||
|
}
|
||||||
|
|
||||||
|
% Wir wollen Latex beim Setzen des Literaturverzeichnisses
|
||||||
|
% nicht ganz so pingelig sein lassen, da wir enge Spalten verwenden
|
||||||
|
\newcommand{\orig@bibliography}{}
|
||||||
|
\let\orig@bibliography\bibliography % Alte Definition in orig@... speichern
|
||||||
|
\renewcommand{\bibliography} % \bibliography neu definieren
|
||||||
|
{
|
||||||
|
\tolerance 1414 % Toleranzwerte setzen (siehe l2tabu)
|
||||||
|
\hbadness 1414 % ...
|
||||||
|
\emergencystretch 1.5em % ...
|
||||||
|
\hfuzz 0.3pt % ...
|
||||||
|
\widowpenalty=10000 % ...
|
||||||
|
\vfuzz \hfuzz % ...
|
||||||
|
\raggedbottom % ...
|
||||||
|
\orig@bibliography % und das Originalkommando aufrufen
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
% Hier werden die Kapitel eingebunden
|
||||||
|
% Bei Bedarf weitere Kapitel eintragen
|
||||||
|
\input{chapters/1_node-red_allgeimein.tex}
|
||||||
|
\input{chapters/2_flow.tex}
|
||||||
|
\input{chapters/3_subflows.tex}
|
||||||
|
\input{chapters/4_vergleich.tex}
|
||||||
|
\input{chapters/5_fazit.tex}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
\section{Node-RED - Allgemein}
|
||||||
|
\label{kevin:node-red-allgemein}
|
||||||
|
Node-RED ist ein Werkzeug für die datenflussbasierte visuelle Programmierungmiteinerbrowser-basierte Entwicklungsumgebung.
|
||||||
|
Mit Node-RED ist es möglich verschiedene Geräte, APIs (Programmierschnittstellen)und Onlinedienste in einer grafischen Oberflächeauf einfache Weise virtuell zu verbinden.
|
||||||
|
Dadurch ist es unter der Verwendung der bereits vorinstallierten „Packages“ (Pakete mit bestimmten Funktionsweisen) Datenflüsse logisch miteinander zu verknüpfen.
|
||||||
|
Zusätzlichbesteht die Möglichkeit eigene Nodes in JavaScript zu programmieren oder mithilfe des bereits integrierten Palletten Manager benötigte Nodes nachträglich zu installieren.
|
||||||
|
Da Node-RED sehr leichtgewichtig ist, eignet es sich ideal für die Nutzung auf kostengünstiger Hardware wie dem Raspberry Pi.
|
||||||
|
|
||||||
|
\subsection{Node-RED Vorteile}
|
||||||
|
\label{kevin:node-red-vorteile}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Einfache Handhabung via Drag and Drop
|
||||||
|
\item Ideal für die Implementierung von einfachen Abläufen
|
||||||
|
\item Einfach zu erweitern
|
||||||
|
\item Schnelle Gestaltung von interessanten Weboberflächen
|
||||||
|
\item plattformübergreifend, da Node.js als Runtime
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Node-RED in der Projektarbeit}
|
||||||
|
\label{kevin:node-red-in-projektarbeit}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/anwendungsschema-node-red.png}
|
||||||
|
\caption{Anwendungsschema von Node-RED in der Projektarbeit}
|
||||||
|
\label{fig:anwendungsschema-node-red}
|
||||||
|
\end{figure}
|
||||||
|
Das Tool Node-RED wird auf einem Raspberry Pi installiert, welcher als Webserver für die Erstellung des Dashboards dient.
|
||||||
|
In der Entwicklungsoberfläche werden die logischen Programmabläufe via „Drag and Drop" erstellt und Funktionsabläufe in der Programmiersprache JavaScript entwickelt.
|
||||||
|
Die Sensordaten werden von den Modulen (HomeMatic und eigene Sensoren) an den Raspberry weitergleitet und abschließend in Node-RED verarbeitet und visualisiert.
|
||||||
|
|
||||||
|
\subsection{Node-RED Installation/Inbetriebnahme}
|
||||||
|
\label{kevin:node-red-installation}
|
||||||
|
Die Installation von Node-RED wird in Abschnitt \ref{lenny:node-red-installation} auf Seite \pageref{lenny:node-red-installation} beschrieben.
|
||||||
|
|
||||||
|
\subsection{Browserbasierte Entwicklungsoberfläche}
|
||||||
|
\label{kevin:node-red-oberflaeche}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/ss-node-red.png}
|
||||||
|
\caption{Aufbau der Entwicklungsoberfläche in Node-RED}
|
||||||
|
\label{fig:ss-node-red}
|
||||||
|
\end{figure}
|
||||||
|
Nach der Installation ist die Node-RED Oberfläche im Browser unter folgender URL zu erreichen:
|
||||||
|
\begin{lstlisting}
|
||||||
|
http://ip-des-raspberry:1880
|
||||||
|
\end{lstlisting}
|
||||||
|
Die schlicht gehaltene Node-RED-Entwicklungsoberfläche lässt sich in drei Bereiche einteilen.
|
||||||
|
|
||||||
|
Im 1. Bereich (gelbe Markierung) ist die Node-Palette welche die mitgelieferten Nodes enthält.
|
||||||
|
|
||||||
|
Der 2. Bereich beschreibt den eigentlichen Arbeitsbereich, dieser Bereich wird Flow, Arbeitsblatt oder Workspace genannt.
|
||||||
|
Hier werden die logischen datenflussbasierten Abläufe und Funktionen mittels Drag \& Drop der Nodes aus der Node-Palette erstellt.
|
||||||
|
Durch das Plus-Zeichen in der oberen rechten Ecke im roten Bereich können mehrere Arbeitsblätter erstellt werden.
|
||||||
|
|
||||||
|
Im letzten 3. Bereich befindet sich das Informations-bzw. Debug-Panel.
|
||||||
|
Dort werden Info-Texte zu den einzelnen Nodes und Anleitungen für die Konfiguration angezeigt.
|
||||||
|
Der Debug-Tab zeigt die Ausgaben der Debug-Nodes und listet interne Fehler auf.
|
||||||
|
Durch die rote Schaltfläche „Implementieren“ oben rechts, werden alle Flows in den Raspberry Pi geladen und ausgeführt.
|
||||||
|
|
||||||
|
\subsection{Visualisierung inNode-RED}
|
||||||
|
\label{kevin:node-red-visualisierung}
|
||||||
|
In der Grundausstattung sind bereits einige nützliche Nodes installiert.
|
||||||
|
Werden für die Nutzung noch weitere Nodes benötigt, können diese direkt in der Web-Oberfläche nachinstalliert werden.
|
||||||
|
Hier kann nach neuen Nodes gesucht und diese per Mausklick installiert werden.
|
||||||
|
|
||||||
|
Für die Anzeige von Visualisierungen in dem Dashboard wird das „node-red-dashboard“ Package benötigt.
|
||||||
|
Für die Installation wird der Menüpunkt „Palette verwalten“ im Drop Down Menü oben rechts (3 horizontale Striche) ausgewählt.
|
||||||
|
Durch die Eingabe „dashboard“ in die Suchleiste, wird das notwendige Package angezeigt.
|
||||||
|
Mit der Bestätigung „installieren“ werden die benötigten Module in Node-RED hinzugefügt.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/pakete-verwalten-node-red.png}
|
||||||
|
\caption{Palette verwalten Dienst für nachträgliche Package-Installationen}
|
||||||
|
\label{fig:pakete-verwalten-node-red}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\subsection{Node-RED Bedienoberfläche}
|
||||||
|
\label{kevin:node-red-bedienoberflaeche}
|
||||||
|
Aufgerufen wird die Web-Oberflächemit folgender URL:
|
||||||
|
\begin{lstlisting}
|
||||||
|
http://ip-des-raspberry:1880/ui
|
||||||
|
\end{lstlisting}
|
||||||
|
Der Aufbauder Gruppen innerhalb des User Interfaces kann über das Dashboard-Panel, welches als weiterer Tab im Info-/Debug-Panel angezeigt wird, vorgenommen werden.
|
||||||
|
In der Konfiguration können die Elemente auf mehrere Tabs und in Gruppen aufgeteiltwerden.
|
||||||
|
Gehören Elemente der gleichen Gruppe an, teilen sie sich eine Kachel in der Bedienoberfläche.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/ss-node-red-dashboard.png}
|
||||||
|
\caption{Bedienoberfläche im Webbrowser}
|
||||||
|
\label{fig:ss-node-red-dashboard}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Für die Projektarbeit wurde das Dashboard in vier logisch zusammenhängende Gruppen eingeteilt.
|
||||||
|
Jede einzelne Gruppe repräsentiert eine Auswertung von Sensordaten mit visueller Darstellung.
|
||||||
|
Dabei wurde zwischen kommerziellen und den selbstgebauten Sensoren differenziert.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Durch die beiden Gruppen \emph{HomeMatic Präsenz \& Tür Sensor} und \emph{HomeMatic Powermeter} werden die kommerziellen Sensoren von HomeMatic vertreten, darunter ist der Präsenz Melder, die Steckdose und der Tür Sensor.
|
||||||
|
Die verschiedenen Sensoren von HomeMatic wurden von dem Kommilitonen Sebastian Dohle angelernt und angebracht.
|
||||||
|
Innerhalb der \emph{HomeMatic Powermeter} Gruppe werden die Sensordaten der Steckdose angezeigt, darunter auch ein grafischer Verlauf der Netzspannung und des Stroms in einer Time-Chart.
|
||||||
|
Bei \emph{HomeMatic Präsenz \& Tür Sensor} werden die Standard-Ausgabewerte der Sensorenangezeigt.
|
||||||
|
Darunter der aktuelle Status des Tür-Sensors, also ob offen oder zu und die aktuelle Präsenzmeldung.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Hinsichtlich der Gruppe \emph{Dashbaord} (links) sind die grafischen Auswertungen der eigenen Sensordaten dargestellt.
|
||||||
|
Hierbei ist in der oberen Hälfte der Ultraschall Sensor visualisiert.
|
||||||
|
Es werden nur die vereinfachten Werte 1 oder 0 angezeigt.
|
||||||
|
Das gleiche gilt für den PIR Sensor in der unteren Hälfte.
|
||||||
|
Die Sensordaten 1 oder 0 zeigen an ob der jeweilige Sensor ausgelöst hat oder nicht.
|
||||||
|
Rechts dazu in der \emph{Eigene Sensoren} Gruppe steht die textuelle Auswertung und eine logische Auswertung mit „Person(en) im Raum“ oder „Keine Person(en) im Raum“.
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
\section{Flow/Ausarbeit}
|
||||||
|
\label{kevin:flow}
|
||||||
|
In Node-RED gibt es die Möglichkeit Arbeitsblätter anzulegen.
|
||||||
|
Dadurch lassen sich die verschiedenen Sensoren in logische Gruppen verpacken.
|
||||||
|
Das Projekt bekommt dadurch eine bessere Übersicht und Strukturierung.
|
||||||
|
Jeder einzelner Flow beschreibt dabei eine eigene Logik, welche die Sensordaten auswertet und visualisiert.
|
||||||
|
|
||||||
|
Bei der datenflussbasierten Entwicklung in Node-RED wird der Datenaustausch durch sogenannte Message-Objekte umgesetzt.
|
||||||
|
|
||||||
|
\subsection{MainDashboard -Flow}
|
||||||
|
\label{kevin:maindashboard-flow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/main-dashboard-flow.png}
|
||||||
|
\caption{Auswertung und Visualisierung des PIR-und Ultraschallsensors}
|
||||||
|
\label{fig:main-dashboard-flow}
|
||||||
|
\end{figure}
|
||||||
|
Für die visuelle Darstellung des PIR- und Ultraschallsensors ist das Flow „MainDashboard“ zuständig(s. Abb. \ref{fig:main-dashboard-flow}).
|
||||||
|
Zu Beginn sind im roten Bereich die beiden Subflows „UltraSchallSensor“ und „PirSensor“ erkennbar.
|
||||||
|
Diese repräsentieren dabei eine selbst entwickelte Logik und geben ihre Sensordaten weiter an das gelbe „merge“ Node im blauen Kasten.
|
||||||
|
Durch den „merge“ Baustein werden die beiden Datenströme miteinander kombiniert.
|
||||||
|
Dadurch können diese Datenstöme anschließend leichter durch den „PräsenzFunction“ Node ausgewertet werden.
|
||||||
|
Die beiden äußeren Zweige, haben eine einfache „TextFunction“ welche eine textuelle Darstellung der beiden Werte „Ausgelöst“ und „Nicht Ausgelöst“ in der UI anzeigen.
|
||||||
|
|
||||||
|
\subsection{HMPresenceSensor–Flow}
|
||||||
|
\label{kevin:hmpresencesensor-flow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/hmpresencesensor-flow.png}
|
||||||
|
\caption{Flow für den Homematic IP Präsenzmelder}
|
||||||
|
\label{fig:hmpresencesensor-flow}
|
||||||
|
\end{figure}
|
||||||
|
In diesem Flow (s. Abb. \ref{fig:hmpresencesensor-flow}) wird die Logik von dem HomeMatic IP Präsenzmelder definiert.
|
||||||
|
Zu Beginn wird eine Subscription durch die beiden violetten Nodes (linke Seite) erstellt, somit erhalten wir die Daten von vorgenanntem Sensor.
|
||||||
|
Je nachdem, welchen Wert wir erhalten wird dementpsrechend eine Weiterverarbeitung durch Function Nodes vorgenommen und anschließend durch einen Text auf der Bedienoberfläche dargestellt.
|
||||||
|
|
||||||
|
\subsection{HMDoorSensor-Flow}
|
||||||
|
\label{kevin:hmdoorsensor-flow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/hmdoorsensor-flow.png}
|
||||||
|
\caption{HomeMatic Türsensor Flow}
|
||||||
|
\label{fig:hmdoorsensor-flow}
|
||||||
|
\end{figure}
|
||||||
|
Der HomeMatic Funk Tür-/Fensterkontakt liefert durch die Subscription „DoorSensor“ (violetterNode) einen Boolean-Wert, ob die Türe offen oder geschlossen ist.
|
||||||
|
Der weitergeleitete Wert wird in der „Function“ Node durch den String „Geschlossen“ oder „Offen“ ersetzt und als Text in der Bedienoberfläche dargestellt.
|
||||||
|
|
||||||
|
\subsection{HMPowermeter–Flow}
|
||||||
|
\label{kevin:hmpowermeter-flow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/hmpowermeter-flow.png}
|
||||||
|
\caption{Sensorauswertung für den Homematic Funk-Schaltaktor}
|
||||||
|
\label{fig:hmpowermeter-flow}
|
||||||
|
\end{figure}
|
||||||
|
Der Homematic Funk-Schaltaktor bietet die Funktionalität Daten zu senden und zu empfangen.
|
||||||
|
Die blaue Markierung beschreibt die Funktion Daten durch die Subscriptions (violetten Nodes) zu empfangen.
|
||||||
|
Dabei werden die Werte Netzspannung, Strom, Leistung und Frequenz auf der Bedienoberfläche dargestellt.
|
||||||
|
Die Netzspannung und der Strom haben zusätzlich eine grafische Auswertung erhalten, um dadurch den zeitlichen Verlauf der Werte besser verfolgen zu können.
|
||||||
|
Für das Ein-/ Ausschalten des Funk-Schaltaktors wird ein Switch-Schalter innerhalb der Bedienoberfläche erstellt.
|
||||||
|
Das Schalten erfolgt durch die Booleanwerte True/False und wird anschließend mit einem sogenannten Thema/Topic zu RedMatic weitergeleitet.
|
||||||
|
Der„StateText“ Node zeigt in der Bedienoberfläche den aktuellen Verbindungsstatus des Homematic Moduls an.
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/hmpowermeter-redmatic-flow.png}
|
||||||
|
\caption{Weiterleitungen in RedMatic}
|
||||||
|
\label{fig:hmpowermeter-redmatic-flow}
|
||||||
|
\end{figure}
|
||||||
|
Die Weiterleitung der Sensordaten an Node-RED erfolgt in RedMatic in Abschnitt \ref{lennart:redmatic} auf Seite \pageref{lennart:redmatic}.
|
||||||
|
Wie bei der blauen Markierung erkennbar ist, werden in diesem Flow die Sensordaten des Homematic Modulsdirekt an Node-RED weitergeleitet und dort empfangen.
|
||||||
|
Die rote Markierung zeigt die Logik für das Umschalten des Funk-Schaltkontaktes.
|
||||||
|
Zu Beginn wird eine Subscription auf Node-RED gemacht, welche die Daten des Switch-Schalters empfängt.
|
||||||
|
Anschließend werden die empfangenen Boolean-Werte an das Modul gesendet und umgesetzt.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
\section{Subflow}
|
||||||
|
\label{kevin:subflow}
|
||||||
|
Mit der Funktion Subflow bietet Node-RED die Möglichkeit, kleinere untergeordnete Flows zu erstellen die in einem Projekt auf verschiedenen Arbeitsblättern wiederverwendet werden können.
|
||||||
|
Durch Subflows können komplexere Abläufe und Auswertungen in einem Node abstrahiert werden.
|
||||||
|
Größere Projekte werden dadurch vereinfacht und klarer strukturiert.
|
||||||
|
|
||||||
|
\subsection{PirSensor–Subflow}
|
||||||
|
\label{kevin:pir-subflow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/pir-subflow.png}
|
||||||
|
\caption{Subflow PIR-Sensor}
|
||||||
|
\label{fig:pir-subflow}
|
||||||
|
\end{figure}
|
||||||
|
Für die Anzeige der Sensordaten in einer Time-Chart, wird eine Subscription durchgeführt um die Daten zu empfangen.
|
||||||
|
Durch den anschließenden „NachrichtenBegrenzung“ Node wird nur eine bestimmte Anzahl an Daten pro Zeiteinheit weitergeleitet, überschüssige Daten werden verworfen.
|
||||||
|
|
||||||
|
Die folgend „PirFunction“ filtert die Daten und leitet vereinfachte Werte weiter an das TimeChart und an den Ausgang des Subflows.
|
||||||
|
\lstinputlisting{code/pirfunction.js}
|
||||||
|
Das „PirFunction“ Node wertet die eintreffenden Daten bei der \inline{if} Anweisung aus und erstellt ein abstrahiertes Messageobjekt für die Weiterverarbeitung.
|
||||||
|
Die \inline{If-Else}-Funktion in den Zeilen 3-17 und 18-32 wird unterschieden ob ein PIR-Sensor ausgelöst hat oder nicht, demnach wird ein neues Objekt mit dem Namen \inline{newMsg} erstellt.
|
||||||
|
Mit \inline{return newMsg} werden die Daten weiter an den Ausgang und der Time-Chart geschickt.
|
||||||
|
|
||||||
|
\subsection{UltraSchallSensor -Subflow}
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.75\textwidth]{images/ultraschall-subflow.png}
|
||||||
|
\caption{Ultraschallsensor Subflow}
|
||||||
|
\label{fig:ultraschall-subflow}
|
||||||
|
\end{figure}
|
||||||
|
Das Arbeitsblatt für den „UltraSchallSensor“ wurde in vier logische Untergruppen eingeteilt (siehe farbige Markierungenin Abb. \ref{fig:ultraschall-subflow}).
|
||||||
|
|
||||||
|
Innerhalb der grünen Markierung werden die Ultraschallsensordaten durch die Subscription empfangen.
|
||||||
|
Durch das anschließende „Function“ Node wird der Datenstrom aufgeteilt und die Sensoren einzeln ausgewertet.
|
||||||
|
Durch die \inline{Sensor2_I} und \inline{Sensor1_A} Funktion werden die einzelnen Datenströme ausgewertet.
|
||||||
|
Da der eine Ultraschallsensor eine zu große Schwankung aufwies ist dieser nicht weiter mit in die Auswertung der Logik eingegangen, dadurch wurde in der \inline{Sensor1_A} Funktion eine Weiterleitung mit \inline{return 0} definiert.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Die türkise Markierung, unten rechts, definiert die Auswertungschart.
|
||||||
|
|
||||||
|
\paragraph{}
|
||||||
|
Die Graue Markierung verbindet die Ausgabedaten von dem Ultraschallsensor und HomeMatic Funk Tür-/Fensterkontakt.
|
||||||
|
Durch das „merge“ Node werden die beiden Datenströme zusammengeführt und in der „OutputFunction“ verarbeitet.
|
||||||
|
Nach der Verarbeitung werden die Ergebnisse an den Output geleitet.
|
||||||
|
\lstinputlisting{code/sensor2_l.js}
|
||||||
|
Die „Sensor2\_I“ Function verarbeitet von einem der beiden Ultraschall\-sensoren die Daten.
|
||||||
|
Innerhalb der Zeilen 4 und 5 werden zuerst die Konstanten festgelegt, welche die Distanz und eine Schwankungstoleranz definieren.
|
||||||
|
|
||||||
|
In den Zeilen 9 bis 22 wird durch eine Kontrollstruktur geprüft, ob jemand den Ultraschallsensor passiert hat.
|
||||||
|
Sobald eine Person vorbeigelaufen ist, wird ein neues Message-Objekt erstellt und mit den dem payload-Wert true gefüllt.
|
||||||
|
\lstinputlisting{code/sensor2_l-output.js}
|
||||||
|
Die „OutputFunction“ beschreibt die Logikauswertung der Daten von dem HomeMatic Funk Tür-/ Fensterkontakt und des Ultraschallsensors.
|
||||||
|
Durch die Überprüfung mit der Kontrollstruktur wird definiert, wenn die Türe geöffnet ist und jemand den Ultraschallsensor passiert.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
\section{Vergleich zu anderen IoT Tools}
|
||||||
|
\label{kevin:vergleich}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
\section{Node-RED Fazit}
|
||||||
|
\label{kevin:fazit}
|
||||||
|
Mit dem Werkzeugbaukasten Node-RED ist es möglich innerhalb von kürzester Zeit optisch ansprechende Dashboards zu erstellen.
|
||||||
|
Die große Auswahl an Bausteinen bietet eine gute Voraussetzung für das Entwerfen übergreifender Anwendungen.
|
||||||
|
Das Spektrum reicht von der Ansteuerung von Hardware bis zu High-Level-APIs wie Social Media Plattformen.
|
||||||
|
Node-RED kann durch eigene Scripte erweitert werden, diese werden in der Programmiersprache JavaScript geschrieben.
|
||||||
|
Für die Projektarbeit hat sich Node-RED sehr gut bewährt, weil keine expliziten Programmierkenntnisse vorausgesetzt sind.
|
||||||
|
Weiterhin stellt der Plattform-Anbieter ausführliche Anleitungen und Tutorials bereit.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
var newMsg;
|
||||||
|
|
||||||
|
if(msg.payload === "10" || msg.payload === "00")
|
||||||
|
{
|
||||||
|
if(msg.payload === "10")
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "Pir Sensor 1 & 2",
|
||||||
|
payload: 1 }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "Pir Sensor 1 & 2",
|
||||||
|
payload: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(msg.payload === "01" || msg.payload === "11")
|
||||||
|
{
|
||||||
|
if(msg.payload === "11")
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "Pir Sensor 1 & 2",
|
||||||
|
payload: 1 }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "Pir Sensor 1 & 2",
|
||||||
|
payload: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newMsg;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
var newMsg;
|
||||||
|
if(Array.isArray(msg.payload))
|
||||||
|
{
|
||||||
|
if(msg.payload[0].topic === "UltraschallSensor_Output" /* && msg.payload[1].topic === "DoorSensor" */)
|
||||||
|
{
|
||||||
|
if(msg.payload[0].state === true /* && msg.payload[1].state === true */)
|
||||||
|
{
|
||||||
|
newMsg = { topic: "Output",
|
||||||
|
val: 1,
|
||||||
|
state: true }
|
||||||
|
|
||||||
|
return newMsg;
|
||||||
|
} else {
|
||||||
|
newMsg = { topic: "Output",
|
||||||
|
val: 0,
|
||||||
|
state: false }
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if(msg.payload[1].topic === "UltraschallSensor_Output" /* && msg.payload[1].topic === "DoorSensor" */)
|
||||||
|
{
|
||||||
|
if(msg.payload[0].state === true /* && msg.payload[1].state === true */)
|
||||||
|
{
|
||||||
|
newMsg = { topic: "Output",
|
||||||
|
val: 1,
|
||||||
|
state: true }
|
||||||
|
|
||||||
|
return newMsg;
|
||||||
|
} else {
|
||||||
|
newMsg = { topic: "Output",
|
||||||
|
val: 0,
|
||||||
|
state: false }
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newMsg = { topic: "Output", val: 0, state: false }
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//'Innerer' Ultraschall-Sensor (Sensor Richtung Raum)
|
||||||
|
//Info: Neue Message wird weitergeleitet. Auswertung mit Bedingungsprüfung
|
||||||
|
|
||||||
|
const Distanz = 115; //eingestellte Entfernung (Programmierung in ArduinoStudio)
|
||||||
|
const Toleranz = 10; //Schwankungs-Toleranz
|
||||||
|
|
||||||
|
var newMsg; //Neue Message für neue Werte erstellen
|
||||||
|
|
||||||
|
if (msg.payload < (Distanz - Toleranz))//ausgelöst
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "UltraschallSensor_Innen",
|
||||||
|
payload: true,
|
||||||
|
val: msg.payload } //msg.payload -> aktuellen Wert mitliefern
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newMsg = {
|
||||||
|
topic: "UltraschallSensor_Innen",
|
||||||
|
payload: false,
|
||||||
|
val: msg.payload } //msg.payload -> aktuellen Wert mitliefern
|
||||||
|
}
|
||||||
|
return newMsg;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
% Details der Arbeit
|
||||||
|
\title{Präsenzerkennung im Rahmen einer modularen Smarthome Umgebung}
|
||||||
|
|
||||||
|
\author{Sebastian Dohle, Lennart Heimbs, Kevin Holzschuh, Johannes Krug}
|
||||||
|
\date{März-September 2019}
|
||||||
|
|
||||||
|
\betreuer{Prof.-Dr. Oliver Hofmann, B.-Eng. Martin Wimmer}
|
||||||
|
\semester{Sommersemester 2019}
|
||||||
|
\keywords{Smarthome, Raspberry Pi, Homematic, Homegear, Node-Red, Präsenz\-erkennung, ESP8266, MQTT}
|
||||||
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 481 KiB |
|
After Width: | Height: | Size: 708 KiB |
@@ -0,0 +1,49 @@
|
|||||||
|
\usepackage{listings, xcolor, realboxes, xpatch}
|
||||||
|
|
||||||
|
\lstset{literate=
|
||||||
|
{á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ú}{{\'u}}1
|
||||||
|
{Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ú}{{\'U}}1
|
||||||
|
{à}{{\`a}}1 {è}{{\`e}}1 {ì}{{\`i}}1 {ò}{{\`o}}1 {ù}{{\`u}}1
|
||||||
|
{À}{{\`A}}1 {È}{{\'E}}1 {Ì}{{\`I}}1 {Ò}{{\`O}}1 {Ù}{{\`U}}1
|
||||||
|
{ä}{{\"a}}1 {ë}{{\"e}}1 {ï}{{\"i}}1 {ö}{{\"o}}1 {ü}{{\"u}}1
|
||||||
|
{Ä}{{\"A}}1 {Ë}{{\"E}}1 {Ï}{{\"I}}1 {Ö}{{\"O}}1 {Ü}{{\"U}}1
|
||||||
|
{â}{{\^a}}1 {ê}{{\^e}}1 {î}{{\^i}}1 {ô}{{\^o}}1 {û}{{\^u}}1
|
||||||
|
{Â}{{\^A}}1 {Ê}{{\^E}}1 {Î}{{\^I}}1 {Ô}{{\^O}}1 {Û}{{\^U}}1
|
||||||
|
{Ã}{{\~A}}1 {ã}{{\~a}}1 {Õ}{{\~O}}1 {õ}{{\~o}}1
|
||||||
|
{œ}{{\oe}}1 {Œ}{{\OE}}1 {æ}{{\ae}}1 {Æ}{{\AE}}1 {ß}{{\ss}}1
|
||||||
|
{ű}{{\H{u}}}1 {Ű}{{\H{U}}}1 {ő}{{\H{o}}}1 {Ő}{{\H{O}}}1
|
||||||
|
{ç}{{\c c}}1 {Ç}{{\c C}}1 {ø}{{\o}}1 {å}{{\r a}}1 {Å}{{\r A}}1
|
||||||
|
{€}{{\euro}}1 {£}{{\pounds}}1 {«}{{\guillemotleft}}1
|
||||||
|
{»}{{\guillemotright}}1 {ñ}{{\~n}}1 {Ñ}{{\~N}}1 {¿}{{?`}}1
|
||||||
|
}
|
||||||
|
|
||||||
|
\definecolor{codegreen}{rgb}{0,0.6,0}
|
||||||
|
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
|
||||||
|
\definecolor{codepurple}{rgb}{0.58,0,0.82}
|
||||||
|
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
|
||||||
|
|
||||||
|
\lstdefinestyle{listingstyle}{
|
||||||
|
backgroundcolor=\color{backcolour},
|
||||||
|
commentstyle=\color{codegreen},
|
||||||
|
keywordstyle=\color{magenta},
|
||||||
|
numberstyle=\tiny\color{codegray},
|
||||||
|
stringstyle=\color{codepurple},
|
||||||
|
basicstyle=\ttfamily\footnotesize,
|
||||||
|
breakatwhitespace=false,
|
||||||
|
breaklines=true,
|
||||||
|
captionpos=b,
|
||||||
|
keepspaces=true,
|
||||||
|
numbers=left,
|
||||||
|
numbersep=5pt,
|
||||||
|
showspaces=false,
|
||||||
|
showstringspaces=false,
|
||||||
|
showtabs=false,
|
||||||
|
tabsize=2
|
||||||
|
}
|
||||||
|
|
||||||
|
\lstset{style=listingstyle}
|
||||||
|
\def\inline{\lstinline[basicstyle=\ttfamily\footnotesize]}
|
||||||
|
|
||||||
|
\makeatletter
|
||||||
|
\xpretocmd\lstinline{\Colorbox{backcolour}\bgroup\appto\lst@DeInit{\egroup}}{}{}
|
||||||
|
\makeatother
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
\documentclass[11pt]{scrartcl}
|
||||||
|
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[a4paper, right=2cm, top=2cm, left=3cm, bottom=3cm]{geometry}
|
||||||
|
\usepackage[ngerman]{babel}
|
||||||
|
|
||||||
|
\usepackage{arbeit}
|
||||||
|
|
||||||
|
\usepackage{acronym}
|
||||||
|
\usepackage{xpatch}
|
||||||
|
\usepackage{standalone}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usepackage[ngerman]{babel}
|
||||||
|
\usepackage{float}
|
||||||
|
\usepackage{subcaption}
|
||||||
|
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usetikzlibrary{decorations.pathreplacing}
|
||||||
|
\usetikzlibrary{positioning}
|
||||||
|
|
||||||
|
\definecolor{codegreen}{rgb}{0,0.6,0}
|
||||||
|
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
|
||||||
|
\definecolor{codepurple}{rgb}{0.58,0,0.82}
|
||||||
|
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
|
||||||
|
|
||||||
|
\input{includes}
|
||||||
|
|
||||||
|
%% Beginn des eigentlichen Dokumentes
|
||||||
|
%%-----------------------------------
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\makeatletter
|
||||||
|
\xpretocmd\lstinline{\Colorbox{backcolour}\bgroup\appto\lst@DeInit{\egroup}}{}{}
|
||||||
|
\makeatother
|
||||||
|
|
||||||
|
|
||||||
|
\tableofcontents
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\input{chapters.tex}
|
||||||
|
|
||||||
|
%\newpage
|
||||||
|
%\bibliographystyle{plain}
|
||||||
|
%\bibliography{references}
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
@misc {bendel2019smarthome,
|
||||||
|
author = "Prof. Dr. Oliver Bendel",
|
||||||
|
title = "Smart Home",
|
||||||
|
year = "2019",
|
||||||
|
month = "Januar",
|
||||||
|
howpublished = {https://wirtschaftslexikon.gabler.de/definition/smart-home-54137/version-368820},
|
||||||
|
note = "[Online; aufgerufen 07-08-2019]"
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc {mayer2017smarthomesetup,
|
||||||
|
author = "Patrik Mayer",
|
||||||
|
title = "Homematic mit node-red über homegear",
|
||||||
|
year = "2017",
|
||||||
|
month = "Juli",
|
||||||
|
howpublished = {https://allgeek.de/2017/07/09/homematic-mit-node-red-ueber-homegear/},
|
||||||
|
note = "[Online; aufgerufen 27-08-2019]"
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc {homegear2018documentation,
|
||||||
|
author = "Homegear",
|
||||||
|
title = "Homegear Documentation",
|
||||||
|
year = "2016-2018",
|
||||||
|
howpublished = {https://doc.homegear.eu/overview/},
|
||||||
|
note = "[Online; aufgerufen 28-08-2019]"
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc {alexreinert2019pivccusetup,
|
||||||
|
author = "Alexander Reinert",
|
||||||
|
title = "piVCCU Raspberry Pi Setup",
|
||||||
|
year = "2019",
|
||||||
|
month = "Juni",
|
||||||
|
howpublished = {https://github.com/alexreinert/piVCCU/blob/master/docs/setup/raspberrypi.md},
|
||||||
|
note = "[Online; aufgerufen 09-09-2019]"
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc {sebastianraff2019redmaticinstallation,
|
||||||
|
author = "Sebastian Raff",
|
||||||
|
title = "Installation",
|
||||||
|
year = "2019",
|
||||||
|
month = "März",
|
||||||
|
howpublished = {https://github.com/rdmtc/RedMatic/wiki/Installation},
|
||||||
|
note = "[Online; aufgerufen 10-09-2019]"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
\documentclass[11pt,a4paper,ngerman]{scrartcl}%{article}
|
\documentclass[11pt]{scrartcl}
|
||||||
|
|
||||||
\usepackage[utf8]{inputenc}
|
\usepackage[utf8]{inputenc}
|
||||||
\usepackage[a4paper, right=2cm, top=2cm, left=3cm, bottom=3cm]{geometry}
|
\usepackage[a4paper, right=2cm, top=2cm, left=3cm, bottom=3cm]{geometry}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ python peopleCounter.py -c # Attempts to detect people using webcam
|
|||||||
|
|
||||||
HOGCV = cv2.HOGDescriptor()
|
HOGCV = cv2.HOGDescriptor()
|
||||||
HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
|
HOGCV.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())
|
||||||
|
#HOGCV.set
|
||||||
VERBOSITY = False
|
VERBOSITY = False
|
||||||
|
|
||||||
def detector(image):
|
def detector(image):
|
||||||
@@ -26,7 +27,9 @@ def detector(image):
|
|||||||
|
|
||||||
clone = image.copy()
|
clone = image.copy()
|
||||||
|
|
||||||
(rects, _) = HOGCV.detectMultiScale(image, winStride=(4, 4), padding=(8, 8), scale=1.05)
|
|
||||||
|
|
||||||
|
(rects, _) = HOGCV.detectMultiScale(image, winStride=(2, 2), padding=(8, 8), scale=1.05)
|
||||||
|
|
||||||
# draw the original bounding boxes
|
# draw the original bounding boxes
|
||||||
for (x, y, w, h) in rects:
|
for (x, y, w, h) in rects:
|
||||||
@@ -129,10 +132,31 @@ def videoDetect(cap):
|
|||||||
|
|
||||||
def remoteDetect(image_hub):
|
def remoteDetect(image_hub):
|
||||||
while True:
|
while True:
|
||||||
rpi_name, image = image_hub.recv_image()
|
rpi_name, frame = image_hub.recv_image()
|
||||||
cv2.imshow(rpi_name, image) # 1 window for each RPi
|
|
||||||
cv2.waitKey(1)
|
|
||||||
image_hub.send_reply(b'OK')
|
image_hub.send_reply(b'OK')
|
||||||
|
|
||||||
|
frame = imutils.resize(frame, width=min(400, frame.shape[1]))
|
||||||
|
result = detector(frame.copy())
|
||||||
|
|
||||||
|
# shows the result
|
||||||
|
for (xA, yA, xB, yB) in result:
|
||||||
|
cv2.rectangle(frame, (xA, yA), (xB, yB), (0, 255, 0), 2)
|
||||||
|
|
||||||
|
#if VERBOSITY:
|
||||||
|
cv2.imshow('frame', frame)
|
||||||
|
#cv2.waitKey(0)
|
||||||
|
|
||||||
|
#if time.time() - init >= sample_time:
|
||||||
|
if len(result):
|
||||||
|
print("{} people detected.".format(len(result)))
|
||||||
|
#init = time.time()
|
||||||
|
|
||||||
|
if cv2.waitKey(1) & 0xFF == ord('q'):
|
||||||
|
break
|
||||||
|
|
||||||
|
#cv2.imshow(rpi_name, frame) # 1 window for each RPi
|
||||||
|
#cv2.waitKey(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +1,120 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
#from datetime import datetime, time
|
|
||||||
import time
|
import time
|
||||||
from statistics import median
|
from statistics import median
|
||||||
|
|
||||||
import imutils
|
import imutils
|
||||||
from imutils.video import VideoStream
|
from imutils.video import VideoStream
|
||||||
#from imutils.video import FPS
|
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
frame_timer = None
|
from video_stream import imagezmq
|
||||||
contour_timer = None
|
|
||||||
detection_timer = None
|
|
||||||
|
|
||||||
frame_time = []
|
|
||||||
contour_time = []
|
|
||||||
detection_time = []
|
|
||||||
|
|
||||||
VISUAL_DEBUG = True
|
VISUAL_DEBUG = True
|
||||||
|
BROKER = "141.75.33.126"
|
||||||
|
PORT = 1883
|
||||||
|
|
||||||
def getArgs():
|
def getArgs():
|
||||||
""" Arguments """
|
""" Arguments """
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("-v", "--video", help="path to the video file")
|
ap.add_argument("-v", "--video", help="path to the video file")
|
||||||
ap.add_argument("-a", "--min-area", type=int, default=500, help="minimum area size")
|
ap.add_argument("-a", "--min-area", type=int, default=500, help="minimum area size")
|
||||||
ap.add_argument("-t", "--tracker", type=str, default="csrt", help="OpenCV object tracker type")
|
|
||||||
return vars(ap.parse_args())
|
return vars(ap.parse_args())
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = getArgs()
|
try:
|
||||||
timer = Timer()
|
mqtt_client = mqtt.Client("pi-camera")
|
||||||
|
mqtt_client.connect(BROKER, PORT)
|
||||||
|
except:
|
||||||
|
print("Connection to MQTT-Broker failed.")
|
||||||
|
return 1
|
||||||
|
|
||||||
# if the video argument is None, then the code will read from webcam (work in progress)
|
try:
|
||||||
if args.get("video", None) is None:
|
args = getArgs()
|
||||||
vs = VideoStream(src=0).start()
|
timer = Timer()
|
||||||
time.sleep(2.0)
|
|
||||||
# otherwise, we are reading from a video file
|
|
||||||
else:
|
|
||||||
vs = cv2.VideoCapture(args["video"])
|
|
||||||
|
|
||||||
cv2.namedWindow('Video stream', cv2.WINDOW_NORMAL)
|
|
||||||
detector = DetectionFromFrame(args["min_area"], 0.5)
|
|
||||||
while True:
|
|
||||||
timer.start_frame_timer()
|
|
||||||
detector.currentFrame = vs.read()
|
|
||||||
detector.currentFrame = detector.currentFrame if args.get("video", None) is None else detector.currentFrame[1]
|
|
||||||
# if the frame can not be grabbed, then we have reached the end of the video
|
|
||||||
if detector.currentFrame is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
# resize the frame to 500
|
|
||||||
detector.currentFrame = imutils.resize(detector.currentFrame, width=500)
|
|
||||||
detector.framecounter += 1
|
|
||||||
if detector.framecounter > 1:
|
|
||||||
cnts = detector.prepareFrame()
|
|
||||||
|
|
||||||
for c in cnts:
|
|
||||||
timer.start_contour_timer()
|
|
||||||
bound_rect = cv2.boundingRect(c)
|
|
||||||
#(x, y, w, h) = cv2.boundingRect(c)
|
|
||||||
#initBB2 =(x,y,w,h)
|
|
||||||
|
|
||||||
prott1 = r'ML-Models/MobileNetSSD_deploy.prototxt'
|
|
||||||
prott2 = r'ML-Models/MobileNetSSD_deploy.caffemodel'
|
|
||||||
net = cv2.dnn.readNetFromCaffe(prott1, prott2)
|
|
||||||
|
|
||||||
#trackbox = detector.currentFrame[y:y+h, x:x+w]boundRect[1]
|
|
||||||
trackbox = detector.currentFrame[bound_rect[1]:bound_rect[1]+bound_rect[3],
|
|
||||||
bound_rect[0]:bound_rect[0]+bound_rect[2]]
|
|
||||||
trackbox = cv2.resize(trackbox, (224, 224))
|
|
||||||
#cv2.imshow('image',trackbox)
|
|
||||||
timer.start_detection_timer()
|
|
||||||
blob = cv2.dnn.blobFromImage(cv2.resize(trackbox, (300, 300)),0.007843, (300, 300), 127.5)
|
|
||||||
net.setInput(blob)
|
|
||||||
detections = net.forward()
|
|
||||||
|
|
||||||
for i in np.arange(0, detections.shape[2]):
|
|
||||||
|
|
||||||
detector.detectConfidentiallyPeople(i, detections, bound_rect)
|
|
||||||
timer.stop_detection_timer()
|
|
||||||
|
|
||||||
cv2.rectangle(detector.currentFrame, (bound_rect[0], bound_rect[1]),
|
|
||||||
(bound_rect[0] + bound_rect[2], bound_rect[1] + bound_rect[3]), (255, 255, 0), 1)
|
|
||||||
|
|
||||||
timer.stop_contour_timer()
|
|
||||||
|
|
||||||
|
# if the video argument is None, then the code will read from webcam (work in progress)
|
||||||
|
if args.get("video", None) is None:
|
||||||
|
#vs = VideoStream(src=0).start()
|
||||||
|
image_hub = imagezmq.ImageHub()
|
||||||
|
|
||||||
# show the frame and record if the user presses a key
|
time.sleep(2.0)
|
||||||
cv2.imshow("Video stream", detector.currentFrame)
|
# otherwise, we are reading from a video file
|
||||||
key = cv2.waitKey(1) & 0xFF
|
else:
|
||||||
|
vs = cv2.VideoCapture(args["video"])
|
||||||
|
|
||||||
# if the `q` key is pressed, break from the lop
|
cv2.namedWindow('Video stream', cv2.WINDOW_NORMAL)
|
||||||
if key == ord("q"):
|
detector = DetectionFromFrame(args["min_area"], 0.8)
|
||||||
break
|
while True:
|
||||||
if key == ord("d"):
|
people_count = 0
|
||||||
detector.firstFrame = None
|
timer.start_frame_timer()
|
||||||
#detector.lastFrame = detector.currentFrame
|
if args.get("video", None) is None:
|
||||||
|
rpi_name, detector.currentFrame = image_hub.recv_image()
|
||||||
|
image_hub.send_reply(b'OK')
|
||||||
|
else:
|
||||||
|
detector.currentFrame = vs.read()
|
||||||
|
detector.currentFrame = detector.currentFrame if args.get("video", None) is None else detector.currentFrame[1]
|
||||||
|
# if the frame can not be grabbed, then we have reached the end of the video
|
||||||
|
if detector.currentFrame is None:
|
||||||
|
break
|
||||||
|
|
||||||
timer.print_time()
|
# resize the frame to 500
|
||||||
|
detector.currentFrame = imutils.resize(detector.currentFrame, width=500)
|
||||||
|
detector.framecounter += 1
|
||||||
|
if detector.framecounter > 1:
|
||||||
|
|
||||||
|
cnts = detector.prepareFrame()
|
||||||
|
|
||||||
# finally, stop the camera/stream and close any open windows
|
for c in cnts:
|
||||||
vs.stop() if args.get("video", None) is None else vs.release()
|
bound_rect = cv2.boundingRect(c)
|
||||||
cv2.destroyAllWindows()
|
#(x, y, w, h) = cv2.boundingRect(c)
|
||||||
|
#initBB2 =(x,y,w,h)
|
||||||
|
|
||||||
class Timer:
|
prott1 = r'ML-Models/MobileNetSSD_deploy.prototxt'
|
||||||
def __init__(self):
|
prott2 = r'ML-Models/MobileNetSSD_deploy.caffemodel'
|
||||||
self.frame_timer = None
|
net = cv2.dnn.readNetFromCaffe(prott1, prott2)
|
||||||
self.contour_timer = None
|
|
||||||
self.detection_timer = None
|
|
||||||
|
|
||||||
self.contour_time = []
|
#trackbox = detector.currentFrame[y:y+h, x:x+w]boundRect[1]
|
||||||
self.detection_time = []
|
trackbox = detector.currentFrame[bound_rect[1]:bound_rect[1]+bound_rect[3],
|
||||||
|
bound_rect[0]:bound_rect[0]+bound_rect[2]]
|
||||||
|
trackbox = cv2.resize(trackbox, (224, 224))
|
||||||
|
#cv2.imshow('image',trackbox)
|
||||||
|
blob = cv2.dnn.blobFromImage(cv2.resize(trackbox, (300, 300)),0.007843, (300, 300), 127.5)
|
||||||
|
net.setInput(blob)
|
||||||
|
detections = net.forward()
|
||||||
|
|
||||||
|
for i in np.arange(0, detections.shape[2]):
|
||||||
|
people_count += detector.detectConfidentiallyPeople(i, detections, bound_rect)
|
||||||
|
|
||||||
def start_frame_timer(self):
|
cv2.rectangle(detector.currentFrame, (bound_rect[0], bound_rect[1]),
|
||||||
self.frame_timer = time.time()
|
(bound_rect[0] + bound_rect[2], bound_rect[1] + bound_rect[3]), (255, 255, 0), 1)
|
||||||
|
|
||||||
|
# show the frame and record if the user presses a key
|
||||||
|
cv2.imshow("Video stream", detector.currentFrame)
|
||||||
|
key = cv2.waitKey(1) & 0xFF
|
||||||
|
|
||||||
def get_frame_time(self):
|
# send number of people detected via mqtt
|
||||||
return time.time() - self.frame_timer
|
mqtt_client.publish("/gso/bb/104/Camera", str(people_count))
|
||||||
|
|
||||||
def start_contour_timer(self):
|
# if the `q` key is pressed, break from the lop
|
||||||
self.contour_timer = time.time()
|
if key == ord("q"):
|
||||||
|
break
|
||||||
|
if key == ord("d"):
|
||||||
|
detector.firstFrame = None
|
||||||
|
#detector.lastFrame = detector.currentFrame
|
||||||
|
|
||||||
def stop_contour_timer(self):
|
timer.print_frame_time()
|
||||||
self.contour_time.append(time.time() - self.contour_timer)
|
|
||||||
|
|
||||||
def start_detection_timer(self):
|
|
||||||
self.detection_timer = time.time()
|
|
||||||
|
|
||||||
def stop_detection_timer(self):
|
# finally, stop the camera/stream and close any open windows
|
||||||
self.detection_time.append(time.time() - self.detection_timer)
|
if args.get("video", None) is not None:
|
||||||
|
vs.stop() if args.get("video", None) is None else vs.release()
|
||||||
|
|
||||||
def print_time(self):
|
cv2.destroyAllWindows()
|
||||||
average_contour = 0 if not self.contour_time else sum(self.contour_time)/float(len(self.contour_time))
|
finally:
|
||||||
average_detection = 0 if not self.detection_time else sum(self.detection_time)/float(len(self.detection_time))
|
if args.get("video", None) is None:
|
||||||
|
image_hub.send_reply(b'OK')
|
||||||
median_contour = 0 if not self.contour_time else median(self.contour_time)
|
|
||||||
median_detection = 0 if not self.detection_time else median(self.detection_time)
|
|
||||||
|
|
||||||
total_contour = sum(self.contour_time)
|
|
||||||
total_detection = sum(self.detection_time)
|
|
||||||
|
|
||||||
print("Time for Frame: {:.2f}. Contour Total: {:.2f}. Contour Median: {:.2f}. Contour Average: {:.2f}. Detection Total: {:.2f}. Detection Median: {:.2f}. Detection Average: {:.2f}. ".format(
|
|
||||||
self.get_frame_time(), total_contour, median_contour, average_contour, total_detection, median_detection, average_detection))
|
|
||||||
#print("Contour Times:" + str(timer.contour_time))
|
|
||||||
#print("Detection Times:" + str(timer.detection_time))
|
|
||||||
self.contour_time = []
|
|
||||||
self.detection_time = []
|
|
||||||
|
|
||||||
class DetectionFromFrame:
|
class DetectionFromFrame:
|
||||||
def __init__(self, min_size, confidence):
|
def __init__(self, min_size, confidence):
|
||||||
@@ -205,27 +170,65 @@ class DetectionFromFrame:
|
|||||||
confidence = detections[0, 0, i, 2]
|
confidence = detections[0, 0, i, 2]
|
||||||
|
|
||||||
if confidence > self.confidence_level:
|
if confidence > self.confidence_level:
|
||||||
# extract the index of the class label from the `detections`, then compute the (x, y)-coordinates of
|
# draw a rectangle in green over the detected area
|
||||||
# the bounding box for the object
|
|
||||||
#idx = int(detections[0, 0, i, 1])
|
|
||||||
#box = detections[0, 0, i, 3:7] * np.array([bound_rect[2], bound_rect[3], bound_rect[2], bound_rect[3]])
|
|
||||||
#(startX, startY, endX, endY) = box.astype("int")
|
|
||||||
# draw the prediction on the frame
|
|
||||||
|
|
||||||
#label = "{}: {:.2f}%".format(CLASSES[idx], confidence * 100)
|
|
||||||
label = "{:.2f}%".format(confidence * 100)
|
|
||||||
|
|
||||||
#cv2.rectangle(frame, (startX, startY), (endX, endY), COLORS[idx], 2)
|
|
||||||
cv2.rectangle(self.currentFrame, (bound_rect[0], bound_rect[1]),
|
cv2.rectangle(self.currentFrame, (bound_rect[0], bound_rect[1]),
|
||||||
(bound_rect[0] + bound_rect[2], bound_rect[1] + bound_rect[3]), detected_color, 3)
|
(bound_rect[0] + bound_rect[2], bound_rect[1] + bound_rect[3]), detected_color, 3)
|
||||||
|
|
||||||
y = bound_rect[1] - 15 if bound_rect[1] - 15 > 15 else bound_rect[1] + 15
|
label = "{:.2f}%".format(confidence * 100)
|
||||||
|
|
||||||
#cv2.putText(frame, label, (startX, y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, COLORS[idx], 2)
|
|
||||||
cv2.putText(self.currentFrame, label, (bound_rect[0], bound_rect[1]-5), cv2.FONT_HERSHEY_SIMPLEX, 0.3, detected_color, 1)
|
cv2.putText(self.currentFrame, label, (bound_rect[0], bound_rect[1]-5), cv2.FONT_HERSHEY_SIMPLEX, 0.3, detected_color, 1)
|
||||||
#cv2.imshow("Video stream", self.currentFrame)
|
|
||||||
#print("Person found")
|
|
||||||
|
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __init__(self):
|
||||||
|
self.frame_timer = None
|
||||||
|
self.contour_timer = None
|
||||||
|
self.detection_timer = None
|
||||||
|
|
||||||
|
self.contour_time = []
|
||||||
|
self.detection_time = []
|
||||||
|
|
||||||
|
def start_frame_timer(self):
|
||||||
|
self.frame_timer = time.time()
|
||||||
|
|
||||||
|
def get_frame_time(self):
|
||||||
|
return time.time() - self.frame_timer
|
||||||
|
|
||||||
|
def start_contour_timer(self):
|
||||||
|
self.contour_timer = time.time()
|
||||||
|
|
||||||
|
def stop_contour_timer(self):
|
||||||
|
self.contour_time.append(time.time() - self.contour_timer)
|
||||||
|
|
||||||
|
def start_detection_timer(self):
|
||||||
|
self.detection_timer = time.time()
|
||||||
|
|
||||||
|
def stop_detection_timer(self):
|
||||||
|
self.detection_time.append(time.time() - self.detection_timer)
|
||||||
|
|
||||||
|
def print_frame_time(self):
|
||||||
|
print("Time for Frame: {:.2f}.".format(self.get_frame_time()))
|
||||||
|
|
||||||
|
|
||||||
|
def print_other_times(self):
|
||||||
|
average_contour = 0 if not self.contour_time else sum(self.contour_time)/float(len(self.contour_time))
|
||||||
|
average_detection = 0 if not self.detection_time else sum(self.detection_time)/float(len(self.detection_time))
|
||||||
|
|
||||||
|
median_contour = 0 if not self.contour_time else median(self.contour_time)
|
||||||
|
median_detection = 0 if not self.detection_time else median(self.detection_time)
|
||||||
|
|
||||||
|
total_contour = sum(self.contour_time)
|
||||||
|
total_detection = sum(self.detection_time)
|
||||||
|
|
||||||
|
print("Contour Total: {:.2f}. Contour Median: {:.2f}. Contour Average: {:.2f}.".format(
|
||||||
|
total_contour, median_contour, average_contour))
|
||||||
|
print("Detection Total: {:.2f}. Detection Median: {:.2f}. Detection Average: {:.2f}. ".format(
|
||||||
|
total_detection, median_detection, average_detection))
|
||||||
|
|
||||||
|
self.contour_time = []
|
||||||
|
self.detection_time = []
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||