{"id":543,"date":"2020-04-04T12:44:00","date_gmt":"2020-04-04T10:44:00","guid":{"rendered":"https:\/\/sebastian.fam-knopp.de\/?p=543"},"modified":"2020-07-22T12:53:09","modified_gmt":"2020-07-22T10:53:09","slug":"node-zoocam-ip-kameras-auf-sicherem-weg-oeffentlich-bereitstellen","status":"publish","type":"post","link":"https:\/\/sebastian.knopp.it\/?p=543","title":{"rendered":"node-zoocam &#8211; IP-Kameras auf sicherem Weg \u00f6ffentlich bereitstellen"},"content":{"rendered":"\n<p>Im Rahmen einer gr\u00f6\u00dferen Umgestaltung wurden in den Terrarien des Schulzoos des Collegium Josephinum einige IP-Kameras installiert. Diese sehr g\u00fcnstigen Kameras eines No-Name Herstellers mit Pan- und Tilt-Funktion (Rotation um zwei Achsen) stellten lediglich ein sehr rudiment\u00e4res Web-Interface zur Interaktion bereit, auf dem der Gro\u00dfteil der Einstellungsm\u00f6glichkeiten bereits garnicht funktionierte. Um den Kamera-Stream im Browser betrachten zu k\u00f6nnen, ist zudem ein Flash-Plugin notwendig, was moderne Browser \u00fcberhaupt nicht mehr unterst\u00fctzen. Ziel war es nun, die Bilder dieser Kameras irgendwie \u00f6ffentlich bereitzustellen. Hierzu ben\u00f6tigt man also eine M\u00f6glichkeit die Bilddaten automatisiert abzugreifen um sie geb\u00fcndelt darzustellen. Gleichzeitig w\u00e4re es f\u00fcr die sp\u00e4teren Nutzer sicherlich nett die Kameras auch selbst steuern zu k\u00f6nnen, um sich im Terrarium umzuschauen.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">OnVIF-Schnittstelle<\/h4>\n\n\n\n<p>Mit etwas Recherche stellte sich heraus, dass die Kameras bereits von sich aus den OnVIF-Standard unterst\u00fctzen. Der OnVIF-Standard dient der Spezifikation von Surveillance-Systemen. Neben IP-Kameras geh\u00f6ren dazu bspw. auch Sicherheitst\u00fcren, Sirenen oder Alarmleuchten. Durch Nutzung der OnVIF-Api-Schnittstellen lie\u00df sich die Adresse eines RTSP-Streams <em>(Real-Time Streaming Protocol)<\/em> herausfinden, welchen die Kameras bereitstellen. Dieser Stream kann bspw. mit dem VLC-Player abgerufen werden. Leider gibt es aber bis heute keine M\u00f6glichkeit einen solchen Stream direkt im Browser darzustellen.<\/p>\n\n\n\n<p>Die Schnittstelle bietet noch weitere Features an. So lassen sich hier\u00fcber auch die Pan- und Tilt-Funktionen der Kamera ansteuern. Der Standard sieht hierbei eigentlich eine genaue Definition von Richtung, Geschwindigkeit und Dauer vor. Die Programmierer eines der verwendeten Kameramodelle haben sich aber f\u00fcr eine sehr rudiment\u00e4re Umsetzung dieser Schnittstelle entschieden und unterscheiden lediglich zwischen positiven und negativen Werten f\u00fcr die Richtungsangabe und drehen dabei immer mit fester Dauer und Geschwindigkeit. Bestimmte Wertekombinationen f\u00fcr Geschwindigkeit und Dauer lassen die Kamera sogar einfach neustarten.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">RTSP zu WebRTC<\/h4>\n\n\n\n<p>Mittels Einbindung des \u00f6ffentlichen Projekts <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/mpromonet\/webrtc-streamer\" target=\"_blank\">webrtc-streamer<\/a> gelang es RTSP-Streams zu WebRTC-Streams zu wandeln. Die WebRTC-Schnittstelle dient normalerweise der Umsetzung von direkter Videotelefonie zwischen zwei Browsern, kann also direkt verwendet werden um Livebilder im Browser anzuzeigen.<\/p>\n\n\n\n<p>Den RTSP-Stream wollte ich jedoch aufgrund der kleinen verbauten Chips nicht direkt von den Kameras beziehen, sondern schaltete einen <a rel=\"noreferrer noopener\" href=\"http:\/\/www.live555.com\/proxyServer\/\" target=\"_blank\">Live555-RTSP-ProxyServer<\/a> dazwischen. Diesen kann man auch \u00f6ffentlich erreichbar schalten und so dem Benutzer weiterhin ein eigenst\u00e4ndiges Abrufen des Kamerastreams im VLC-Player mit ausreichend Server-Ressourcen erm\u00f6glichen. F\u00fcr beide Projekte erstellte ich angepasste Docker-Container und programmierte anhand des webrtc-streamer Projekts mein Frontend, sodass es die Streams direkt von dort einbinden konnte.<\/p>\n\n\n\n<p>Da WebRTC auch teils komplexe Netzwerkarchitekturen unterst\u00fctzen k\u00f6nnen muss, ben\u00f6tigt es zwei weitere Dienste, einen STUN- und einen TURN-Server, um einwandfrei zu funktionieren. <\/p>\n\n\n\n<p>Ein <strong>STUN-Server<\/strong> wird ben\u00f6tigt, damit ein Browser mittels eines externen Dienstes seine tats\u00e4chliche \u00f6ffentliche IP-Adresse und Portnummer herausfinden kann, was gerade im Fall von NATing notwendig sein kann. Er macht Client und Server untereinander bekannt. Anschlie\u00dfend kann mittels <a rel=\"noreferrer noopener\" href=\"https:\/\/en.wikipedia.org\/wiki\/Hole_punching_(networking)\" target=\"_blank\">Hole Punching<\/a> versucht werden, die bereits bestehende TCP-Verbindung weiter zu verwenden um so bspw. restriktive interne Firewalls zu umgehen und zwei eigentlich hinter Firewalls versteckte Dienste direkt miteinander zu verbinden. <\/p>\n\n\n\n<p>Klappt das nicht, gibt es als Fallback-M\u00f6glichkeit noch die Nutzung von Relay-Servern (sog. <strong>TURN-Server<\/strong>). Diese m\u00fcssen von Client und Server direkt erreichbar sein, empfangen den Datenverkehr beider Seiten und leiten ihn an die Gegenstelle als dritte Instanz weiter. Dadurch, dass s\u00e4mtlicher Datenverkehr dar\u00fcber laufen muss, m\u00fcssen sie eine entsprechende Bandbreite vorhalten und selbst betrieben werden.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Fallback auf JPEG-Stream<\/h4>\n\n\n\n<p>Sollten alle Stricke rei\u00dfen und eine WebRTC-Verbindung nicht m\u00f6glich sein. Dies kann bspw. auch bei \u00e4lteren Mobilbrowsern der Fall sein, welche schlicht kein WebRTC unterst\u00fctzen, so kann mittels der OnVIF-Screenshot Schnittstelle regelm\u00e4\u00dfig ein aktuelles JPG des Kamerabildes abgerufen und weitergegeben werden. Der Server implementiert dies mit einer entsprechenden Proxyschnittstelle, um die Kameras im Falle vieler Anfragen nicht zu \u00fcberlasten.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Webserver mit node.js<\/h4>\n\n\n\n<p>Der Webserver an sich basiert auf <a rel=\"noreferrer noopener\" href=\"https:\/\/expressjs.com\/de\/\" target=\"_blank\">ExpressJS<\/a> und implementiert eine REST-API, welche die wichtigen Funktionen und \u00f6ffentlichen Adressen der Kameras bereitstellt. Er stellt au\u00dferdem ein Angular-Frontend bereit, welches die REST-API ansteuert und die Kamerastreams entsprechend visualisiert.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Docker Container-Orchestration und Deployment<\/h4>\n\n\n\n<p>W\u00e4hrend der Entwicklung und auch im Produktivbetrieb kommt vor Allem <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.docker.com\/compose\/\" target=\"_blank\">docker-compose<\/a> zum Einsatz. Hiermit l\u00e4sst sich zur Entwicklungszeit schnell das gesamte Projekt bauen und unter Produktivbedingungen testen. Im Produktiveinsatz lassen sich dann recht einfach eine Versionierung und Aktualisierung durchf\u00fchren. Jeder Commit auf den Master-Branch des Projekts l\u00f6st den Bau einer neuen Version des Docker-Images aus, welches dann auf dem Produktivserver \u00fcber die Docker Registry einfach bezogen und gestartet werden kann.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Zugriff auf das Projekt<\/h4>\n\n\n\n<p>Unter <a rel=\"noreferrer noopener\" href=\"https:\/\/webcam.cojobo.net\/\" target=\"_blank\">webcam.cojobo.net<\/a> kann man das Projekt selber ausprobieren. Der Sourcecode ist unter <a rel=\"noreferrer noopener\" href=\"https:\/\/git.knopp.it\/sknopp\/node-zoocam\" target=\"_blank\">auf meinen GitLab-Server einsehbar<\/a>. Im Folgenden sind einige Screenshots des Projekts zu sehen.<\/p>\n\n\n\n<figure class=\"wp-block-gallery columns-2 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"331\" data-attachment-id=\"560\" data-permalink=\"https:\/\/sebastian.knopp.it\/?attachment_id=560\" data-orig-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1.jpg\" data-orig-size=\"1920,621\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"node-zoocam-screenshot1\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;Titelseite&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-300x97.jpg\" data-large-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-1024x331.jpg\" src=\"https:\/\/sebastian.fam-knopp.de\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-1024x331.jpg\" alt=\"\" data-id=\"560\" data-full-url=\"https:\/\/sebastian.fam-knopp.de\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1.jpg\" data-link=\"https:\/\/sebastian.fam-knopp.de\/?attachment_id=560\" class=\"wp-image-560\" srcset=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-1024x331.jpg 1024w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-300x97.jpg 300w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-768x248.jpg 768w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1-1536x497.jpg 1536w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot1.jpg 1920w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"blocks-gallery-item__caption\">Titelseite<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"400\" data-attachment-id=\"563\" data-permalink=\"https:\/\/sebastian.knopp.it\/?attachment_id=563\" data-orig-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2.jpg\" data-orig-size=\"1917,748\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"node-zoocam-screenshot2\" data-image-description=\"\" data-image-caption=\"&lt;p&gt;Kamera Detailansicht mit Steuerung&lt;\/p&gt;\n\" data-medium-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-300x117.jpg\" data-large-file=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-1024x400.jpg\" src=\"https:\/\/sebastian.fam-knopp.de\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-1024x400.jpg\" alt=\"\" data-id=\"563\" data-full-url=\"https:\/\/sebastian.fam-knopp.de\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2.jpg\" data-link=\"https:\/\/sebastian.fam-knopp.de\/?attachment_id=563\" class=\"wp-image-563\" srcset=\"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-1024x400.jpg 1024w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-300x117.jpg 300w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-768x300.jpg 768w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2-1536x599.jpg 1536w, https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-screenshot2.jpg 1917w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"blocks-gallery-item__caption\">Kamera Detailansicht mit Steuerung<\/figcaption><\/figure><\/li><\/ul><figcaption class=\"blocks-gallery-caption\">Screenshots des Projekts<\/figcaption><\/figure>\n\n\n\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container is-layout-flow wp-block-group-is-layout-flow\">\n<p class=\"has-text-color has-cyan-bluish-gray-color\">Titelbild-Icon made by <a href=\"https:\/\/www.flaticon.com\/authors\/eucalyp\">Eucalyp<\/a> from <a href=\"https:\/\/www.flaticon.com\/\">www.flaticon.com<\/a><\/p>\n<\/div><\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Im Rahmen einer gr\u00f6\u00dferen Umgestaltung wurden in den Terrarien des Schulzoos des Collegium Josephinum einige IP-Kameras installiert. Diese sehr g\u00fcnstigen Kameras eines No-Name Herstellers mit Pan- und Tilt-Funktion (Rotation um zwei Achsen) stellten lediglich ein sehr rudiment\u00e4res Web-Interface zur Interaktion bereit, auf dem der Gro\u00dfteil der Einstellungsm\u00f6glichkeiten bereits garnicht funktionierte. Um den Kamera-Stream im Browser [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":564,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"default","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1],"tags":[],"class_list":["post-543","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-allgemein"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/sebastian.knopp.it\/wp-content\/uploads\/2020\/07\/node-zoocam-titleimage.png","jetpack_shortlink":"https:\/\/wp.me\/p4onxe-8L","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/posts\/543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=543"}],"version-history":[{"count":4,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/posts\/543\/revisions"}],"predecessor-version":[{"id":567,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/posts\/543\/revisions\/567"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=\/wp\/v2\/media\/564"}],"wp:attachment":[{"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sebastian.knopp.it\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}