Merge dev branch code named Fossa as next stable Mongoose

This commit is contained in:
Marko Mikulicic committed 2015-09-08 14:34:30 +02:00
1 parent 28eb251ccd
commit 8927c9d22b
126 files changed
+162049 -8502

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
NS=../../mongoose.c
FLAGS = ../../mongoose.c -I../..
CFLAGS=-W -Wall -DNS_ENABLE_THREADS -pthread $(CFLAGS_EXTRA)
PROGS = device_side cloud_side
all: $(PROGS)
device_side: Makefile device_side.c $(NS)
$(CC) device_side.c $(FLAGS) -o $@ $(CFLAGS)
cloud_side: Makefile cloud_side.c $(NS)
$(CC) cloud_side.c $(FLAGS) -o $@ $(CFLAGS)
device_side.exe: Makefile device_side.c $(NS)
cl device_side.c $(FLAGS) /MD /Fe$@
clean:
rm -rf *.gc* *.dSYM *.exe *.obj *.o a.out $(PROGS)
@@ -0,0 +1,72 @@
= Raspberry Pi camera/LED demo
== Overview
The link:/[demo] consists of web app providing access to a webcam and a LED attached to a RaspberryPi.
The device is assumed to have a limited bandwidth towards the server hosting the web app.
== Objective
The demo shows how to use websockets to communicate bidirectionally with an embedded device using standard protocols.
It also shows that it's possible to use Smart.c to develop also the cloud endpoint and expose WebSocket and RESTful APIs
easy to integreate with modern web stacks.
== How it works
image::docs/arch.png[]
There are two components, once with runs on the device (`device_side`) and one that runs on a stronger machine
and with more bandwidth (`cloud_side`).
The device app connects to the cloud app via websocket and sends a new jpeg frame as fast as the underlying `raspistill` camera
grabbing application can handle. The device automatically attempts reconnecting.
The cloud side serves the webapp static pages and serves an MPJEG image on `/mpjg`.
The MPJEG image handler blocks all the clients until a JPEG frame arrives via websocket
and then every client will recieve a copy of the frame.
The web app can turn on and off the LED via a RESTful api accessible via the `/api` handler.
== Installation
=== Server side
----
git clone https://github.com/cesanta/mongoose
cd mongoose/examples/web_demo
make cloud_side && ./cloud_side 0.0.0.0:8080
----
=== Raspberry Pi
The instructions provided here are tailored for the Raspbian distribution.
==== Dependencies
jpegoptim::
apt-get install jpegoptim
camera::
run raspi-config and enable camera
==== LED
In order to access the led on your link:http://www.qdh.org.uk/wordpress/?page_id=15[HotPi]
board you need to export the gpio pins:
----
for i in 22 23 24; do
echo $i >/sys/class/gpio/export
echo out >/sys/class/gpio/gpio$i/direction
chgrp pi /sys/class/gpio/gpio$i/value
done
----
==== Build and run
----
git clone https://github.com/cesanta/mongoose
cd mongoose/examples/web_demo
make device_side && ./device_side yourserver:8080
----
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* This is the cloud endpoint of the Raspberry Pi camera/LED example
* of the Mongoose networking library.
* It is a simple web server, serving both static files, a REST API handler,
* and a WebSocket handler.
*/
#include "mongoose.h"
static struct mg_serve_http_opts web_root_opts;
/*
* Forwards the jpeg frame data to all open mjpeg connections.
*
* Incoming messages follow a very simple binary frame format:
* 4 bytes: timestamp (in network byte order)
* n bytes: jpeg payload
*
* The timestamp is used to compute a lag.
* It's done in a quite stupid way as it requires the device clock
* to be synchronized with the cloud endpoint.
*/
static void push_frame_to_clients(struct mg_mgr *mgr,
const struct websocket_message *wm) {
struct mg_connection *nc;
/*
* mjpeg connections are tagged with the NSF_USER_2 flag so we can find them
* my scanning the connection list provided by the mongoose manager.
*/
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (!(nc->flags & NSF_USER_2)) continue; // Ignore un-marked requests
mg_printf(nc, "--w00t\r\nContent-Type: image/jpeg\r\n"
"Content-Length: %lu\r\n\r\n", (unsigned long) wm->size);
mg_send(nc, wm->data, wm->size);
mg_send(nc, "\r\n", 2);
printf("Image pushed to %p\n", nc);
}
}
/*
* Forwards API payload to the device, by scanning through
* all the connections to find those that are tagged as WebSocket.
*/
static void send_command_to_the_device(struct mg_mgr *mgr,
const struct mg_str *cmd) {
struct mg_connection *nc;
for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {
if (!(nc->flags & NSF_IS_WEBSOCKET)) continue; // Ignore non-websocket requests
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, cmd->p, cmd->len);
printf("Sent API command [%.*s] to %p\n", (int) cmd->len, cmd->p, nc);
}
}
/*
* Main event handler. Receives data events and dispatches to
* the appropriate handler function.
*
* 1. RESTful API requests are handled by send_command_to_the_device.
* 2. requests to /mpeg are established and left open waiting for data to arrive
* from WebSocket.
* 3. WebSocket frames are handled by push_frame_to_clients.
* 4. All other connections are passed to the mg_serve_http handler
* which serves static files.
*/
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct websocket_message *wm = (struct websocket_message *) ev_data;
struct http_message *hm = (struct http_message *) ev_data;
switch (ev) {
case NS_HTTP_REQUEST:
if (mg_vcmp(&hm->uri, "/mjpg") == 0) {
nc->flags |= NSF_USER_2; /* Set a mark on image requests */
mg_printf(nc, "%s",
"HTTP/1.0 200 OK\r\n"
"Cache-Control: no-cache\r\n"
"Pragma: no-cache\r\n"
"Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n"
"Connection: close\r\n"
"Content-Type: multipart/x-mixed-replace; "
"boundary=--w00t\r\n\r\n");
} else if (mg_vcmp(&hm->uri, "/api") == 0 && hm->body.len > 0) {
/*
* RESTful API call. HTTP message body should be a JSON message.
* We should parse it and take appropriate action.
* In our case, simply forward that call to the device.
*/
printf("API CALL: [%.*s] [%.*s]\n", (int) hm->method.len, hm->method.p,
(int) hm->body.len, hm->body.p);
send_command_to_the_device(nc->mgr, &hm->body);
mg_printf(nc, "HTTP/1.0 200 OK\nContent-Length: 0\n\n");
} else {
/* Delegate to the static web server handler for all other paths. */
mg_serve_http(nc, hm, web_root_opts);
}
break;
case NS_WEBSOCKET_FRAME:
printf("Got websocket frame, size %lu\n", (unsigned long) wm->size);
push_frame_to_clients(nc->mgr, wm);
break;
}
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
struct mg_connection *nc;
if (argc != 2) {
fprintf(stderr, "Usage: %s <listening_addr>\n", argv[0]);
exit(EXIT_FAILURE);
}
printf("Listening on: [%s]\n", argv[1]);
mg_mgr_init(&mgr, NULL);
/*
* mg_bind() creates a listening connection on a given ip:port and
* with an attached event handler.
* The event handler will only trigger TCP events until the http
* protocol handler is installed.
*/
if ((nc = mg_bind(&mgr, argv[1], ev_handler)) == NULL) {
fprintf(stderr, "Error binding to %s\n", argv[1]);
exit(EXIT_FAILURE);
}
mg_set_protocol_http_websocket(nc);
web_root_opts.document_root = "./web_root";
/*
* We explicitly hand over control to the Mongoose manager
* in this event loop and we can easily multiplex other activities.
*/
for(;;) {
mg_mgr_poll(&mgr, 1000);
}
return EXIT_SUCCESS;
}
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*/
/*
* This is the device endpoint of the Raspberry Pi camera/LED example
* of the Mongoose networking library.
* It is a simple websocket client, sending jpeg frames obtained from the
* RPi camera and receiving JSON commands through the same WebSocket channel
*/
#include <unistd.h>
#include "mongoose.h"
static int s_poll_interval_ms = 100;
static int s_still_period = 100;
static int s_vertical_flip = 0;
static int s_width = 320;
static int s_height = 180;
static const char *s_mjpg_file = "/var/run/shm/cam.jpg";
static struct mg_connection *client;
/*
* Check if there is a new image available and
* send it to the cloud endpoint if the send buffer is not too full.
* The image is moved in a new file by the jpeg optimizer function;
* this ensures that we will detect a new frame when raspistill writes
* it's output file.
*/
static void send_mjpg_frame(struct mg_connection *nc, const char *file_path) {
static int skipped_frames = 0;
struct stat st;
FILE *fp;
/* Check file modification time. */
if (stat(file_path, &st) == 0) {
/* Skip the frame if there is too much unsent data. */
if (nc->send_mbuf.len > 256) skipped_frames++;
/* Read new mjpg frame into a buffer */
fp = fopen(file_path, "rb");
char buf[st.st_size];
fread(buf, 1, sizeof(buf), fp);
fclose(fp);
/*
* Delete the file so we can detect when raspistill creates a new one.
* mtime granularity is only 1s.
*/
unlink(file_path);
/* Send those buffer through the websocket connection */
mg_send_websocket_frame(nc, WEBSOCKET_OP_BINARY, buf, sizeof(buf));
printf("Sent mjpg frame, %lu bytes after skippping %d frames\n", (unsigned long) sizeof(buf), skipped_frames);
skipped_frames = 0;
}
}
/*
* Turn on or off the LED.
* The LED in this example is an RGB led, so all the colors have to be set.
*/
static void set_led(int v) {
char cmd[512];
snprintf(cmd, sizeof(cmd), "for i in 22 23 24; do"
" echo %d >/sys/class/gpio/gpio$i/value; done", v);
system(cmd);
}
/*
* Parse control JSON and perform command:
* for now only LED on/off is supported.
*/
static void perform_control_command(const char* data, size_t len) {
struct json_token toks[200], *onoff;
parse_json(data, len, toks, sizeof(toks));
onoff = find_json_token(toks, "onoff");
set_led(strncmp("[\"on\"]", onoff->ptr, onoff->len) == 0);
}
/* Main event handler. Sends websocket frames and receives control commands */
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct websocket_message *wm = (struct websocket_message *) ev_data;
switch (ev) {
case NS_CONNECT:
printf("Reconnect: %s\n", * (int *) ev_data == 0 ? "ok" : "failed");
if (* (int *) ev_data == 0) {
/*
* Tune the tcp send buffer size, so that we can skip frames
* when the connection is congested. This helps maintaining a
* reasonable latency.
*/
int sndbuf_size = 512;
if(setsockopt(nc->sock, SOL_SOCKET, SO_SNDBUF,
(void *) &sndbuf_size, sizeof(int)) == -1) {
perror("failed to tune TCP send buffer size\n");
}
mg_send_websocket_handshake(nc, "/stream", NULL);
}
break;
case NS_CLOSE:
printf("Connection %p closed\n", nc);
client = NULL;
break;
case NS_POLL:
send_mjpg_frame(nc, s_mjpg_file);
break;
case NS_WEBSOCKET_FRAME:
printf("Got control command: [%.*s]\n", (int) wm->size, wm->data);
perform_control_command((const char*)wm->data, wm->size);
break;
}
}
/*
* This thread regenerates s_mjpg_file every s_poll_interval_ms milliseconds.
* It is Raspberry PI specific, change this function on other systems.
*/
static void *generate_mjpg_data_thread_func(void *param) {
char cmd[400];
(void) param;
snprintf(cmd, sizeof(cmd), "raspistill -w %d -h %d -n -q 100 -tl %d "
"-t 999999999 -v %s -o %s >/dev/null 2>&1", s_width, s_height,
s_still_period, s_vertical_flip ? "-vf" : "", s_mjpg_file);
for(;;) {
int ret = system(cmd);
if (WIFSIGNALED(ret)) exit(1);
sleep(1);
}
return NULL;
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
char *addr = argv[1];
if (argc < 2) {
fprintf(stderr, "Usage: %s <server_address>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* Start separate thread that generates MJPG data */
mg_start_thread(generate_mjpg_data_thread_func, NULL);
printf("Streaming [%s] to [%s]\n", s_mjpg_file, addr);
mg_mgr_init(&mgr, NULL);
for(;;) {
mg_mgr_poll(&mgr, s_poll_interval_ms);
/* Reconnect if disconnected */
if (!client) {
sleep(1); /* limit reconnections frequency */
printf("Reconnecting to %s...\n", addr);
client = mg_connect(&mgr, addr, ev_handler);
if (client) mg_set_protocol_http_websocket(client);
}
}
return EXIT_SUCCESS;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@@ -0,0 +1,533 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.1">
<title>Raspberry Pi camera/LED demo</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400">
<style>
/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
/* Remove the comments around the @import statement below when using this as a custom stylesheet */
/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400";*/
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,video{display:inline-block}
audio:not([controls]){display:none;height:0}
[hidden],template{display:none}
script{display:none!important}
html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body{margin:0}
a{background:transparent}
a:focus{outline:thin dotted}
a:active,a:hover{outline:0}
h1{font-size:2em;margin:.67em 0}
abbr[title]{border-bottom:1px dotted}
b,strong{font-weight:bold}
dfn{font-style:italic}
hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
mark{background:#ff0;color:#000}
code,kbd,pre,samp{font-family:monospace;font-size:1em}
pre{white-space:pre-wrap}
q{quotes:"\201C" "\201D" "\2018" "\2019"}
small{font-size:80%}
sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
sup{top:-.5em}
sub{bottom:-.25em}
img{border:0}
svg:not(:root){overflow:hidden}
figure{margin:0}
fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
legend{border:0;padding:0}
button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
button,input{line-height:normal}
button,select{text-transform:none}
button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
button[disabled],html input[disabled]{cursor:default}
input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}
input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}
button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
textarea{overflow:auto;vertical-align:top}
table{border-collapse:collapse;border-spacing:0}
*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
html,body{font-size:100%}
body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto}
a:hover{cursor:pointer}
img,object,embed{max-width:100%;height:auto}
object,embed{height:100%}
img{-ms-interpolation-mode:bicubic}
#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none!important}
.left{float:left!important}
.right{float:right!important}
.text-left{text-align:left!important}
.text-right{text-align:right!important}
.text-center{text-align:center!important}
.text-justify{text-align:justify!important}
.hide{display:none}
.antialiased,body{-webkit-font-smoothing:antialiased}
img{display:inline-block;vertical-align:middle}
textarea{height:auto;min-height:50px}
select{width:100%}
p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6}
.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
a{color:#2156a5;text-decoration:underline;line-height:inherit}
a:hover,a:focus{color:#1d4b8f}
a img{border:none}
p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
p aside{font-size:.875em;line-height:1.35;font-style:italic}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
h1{font-size:2.125em}
h2{font-size:1.6875em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
h4,h5{font-size:1.125em}
h6{font-size:1em}
hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
em,i{font-style:italic;line-height:inherit}
strong,b{font-weight:bold;line-height:inherit}
small{font-size:60%;line-height:inherit}
code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em}
ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
ul.square{list-style-type:square}
ul.circle{list-style-type:circle}
ul.disc{list-style-type:disc}
ul.no-bullet{list-style:none}
ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
dl dt{margin-bottom:.3125em;font-weight:bold}
dl dd{margin-bottom:1.25em}
abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
abbr{text-transform:none}
blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
blockquote cite:before{content:"\2014 \0020"}
blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
h1{font-size:2.75em}
h2{font-size:2.3125em}
h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
table thead,table tfoot{background:#f7f8f7;font-weight:bold}
table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table}
.clearfix:after,.float-group:after{clear:both}
*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed}
pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
.keyseq{color:rgba(51,51,51,.8)}
kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.75em;line-height:1.4;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:-.15em .15em 0 .15em;padding:.2em .6em .2em .5em;vertical-align:middle;white-space:nowrap}
.keyseq kbd:first-child{margin-left:0}
.keyseq kbd:last-child{margin-right:0}
.menuseq,.menu{color:rgba(0,0,0,.8)}
b.button:before,b.button:after{position:relative;top:-1px;font-weight:400}
b.button:before{content:"[";padding:0 3px 0 2px}
b.button:after{content:"]";padding:0 2px 0 3px}
p a>code:hover{color:rgba(0,0,0,.9)}
#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table}
#header:after,#content:after,#footnotes:after,#footer:after{clear:both}
#content{margin-top:1.25em}
#content:before{content:none}
#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
#header .details span:first-child{margin-left:-.125em}
#header .details span.email a{color:rgba(0,0,0,.85)}
#header .details br{display:none}
#header .details br+span:before{content:"\00a0\2013\00a0"}
#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
#header .details br+span#revremark:before{content:"\00a0|\00a0"}
#header #revnumber{text-transform:capitalize}
#header #revnumber:after{content:"\00a0"}
#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
#toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
#toc>ul{margin-left:.125em}
#toc ul.sectlevel0>li>a{font-style:italic}
#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
#toc a{text-decoration:none}
#toc a:active{text-decoration:underline}
#toctitle{color:#7a2518;font-size:1.2em}
@media only screen and (min-width:768px){#toctitle{font-size:1.375em}
body.toc2{padding-left:15em;padding-right:0}
#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
#toc.toc2 #toctitle{margin-top:0;font-size:1.2em}
#toc.toc2>ul{font-size:.9em;margin-bottom:0}
#toc.toc2 ul ul{margin-left:0;padding-left:1em}
#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
body.toc2.toc-right{padding-left:0;padding-right:15em}
body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
#toc.toc2{width:20em}
#toc.toc2 #toctitle{font-size:1.375em}
#toc.toc2>ul{font-size:.95em}
#toc.toc2 ul ul{padding-left:1.25em}
body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
#content #toc>:first-child{margin-top:0}
#content #toc>:last-child{margin-bottom:0}
#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
#footer-text{color:rgba(255,255,255,.8);line-height:1.44}
.sect1{padding-bottom:.625em}
@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed}
#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0}
.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)}
table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit}
.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
.admonitionblock>table td.icon{text-align:center;width:80px}
.admonitionblock>table td.icon img{max-width:none}
.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
.exampleblock>.content>:first-child{margin-top:0}
.exampleblock>.content>:last-child{margin-bottom:0}
.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
.sidebarblock>:first-child{margin-top:0}
.sidebarblock>:last-child{margin-bottom:0}
.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
.listingblock pre.highlightjs{padding:0}
.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
.listingblock pre.prettyprint{border-width:0}
.listingblock>.content{position:relative}
.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
.listingblock:hover code[data-lang]:before{display:block}
.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999}
.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"}
table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0}
table.pyhltable td.code{padding-left:.75em;padding-right:0}
pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
pre.pygments .lineno{display:inline-block;margin-right:.25em}
table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
.quoteblock{margin:0 1em 1.25em 1.5em;display:table}
.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
.quoteblock blockquote{margin:0;padding:0;border:0}
.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
.quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
.quoteblock .quoteblock blockquote:before{display:none}
.verseblock{margin:0 1em 1.25em 1em}
.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
.verseblock pre strong{font-weight:400}
.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
.quoteblock .attribution br,.verseblock .attribution br{display:none}
.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.05em;color:rgba(0,0,0,.6)}
.quoteblock.abstract{margin:0 0 1.25em 0;display:block}
.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0}
.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none}
table.tableblock{max-width:100%;border-collapse:separate}
table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0}
table.spread{width:100%}
table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0}
table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0}
table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0}
table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0}
table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0}
table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0}
table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0}
table.frame-all{border-width:1px}
table.frame-sides{border-width:0 1px}
table.frame-topbot{border-width:1px 0}
th.halign-left,td.halign-left{text-align:left}
th.halign-right,td.halign-right{text-align:right}
th.halign-center,td.halign-center{text-align:center}
th.valign-top,td.valign-top{vertical-align:top}
th.valign-bottom,td.valign-bottom{vertical-align:bottom}
th.valign-middle,td.valign-middle{vertical-align:middle}
table thead th,table tfoot th{font-weight:bold}
tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
p.tableblock>code:only-child{background:none;padding:0}
p.tableblock{font-size:1em}
td>div.verse{white-space:pre}
ol{margin-left:1.75em}
ul li ol{margin-left:1.5em}
dl dd{margin-left:1.125em}
dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none}
ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em}
ul.checklist li>p:first-child>.fa-check-square-o:first-child,ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
ul.checklist li>p:first-child>input[type="checkbox"]:first-child{position:relative;top:1px}
ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden}
ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block}
ul.inline>li>*{display:block}
.unstyled dl dt{font-weight:400;font-style:normal}
ol.arabic{list-style-type:decimal}
ol.decimal{list-style-type:decimal-leading-zero}
ol.loweralpha{list-style-type:lower-alpha}
ol.upperalpha{list-style-type:upper-alpha}
ol.lowerroman{list-style-type:lower-roman}
ol.upperroman{list-style-type:upper-roman}
ol.lowergreek{list-style-type:lower-greek}
.hdlist>table,.colist>table{border:0;background:none}
.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
td.hdlist1{padding-right:.75em;font-weight:bold}
td.hdlist1,td.hdlist2{vertical-align:top}
.literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1}
.colist>table tr>td:last-of-type{padding:.25em 0}
.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
.imageblock>.title{margin-bottom:0}
.imageblock.thumb,.imageblock.th{border-width:6px}
.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
.image.left{margin-right:.625em}
.image.right{margin-left:.625em}
a.image{text-decoration:none}
span.footnote,span.footnoteref{vertical-align:super;font-size:.875em}
span.footnote a,span.footnoteref a{text-decoration:none}
span.footnote a:active,span.footnoteref a:active{text-decoration:underline}
#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0}
#footnotes .footnote{padding:0 .375em;line-height:1.3;font-size:.875em;margin-left:1.2em;text-indent:-1.2em;margin-bottom:.2em}
#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none}
#footnotes .footnote:last-of-type{margin-bottom:0}
#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
.gist .file-data>table td.line-data{width:99%}
div.unbreakable{page-break-inside:avoid}
.big{font-size:larger}
.small{font-size:smaller}
.underline{text-decoration:underline}
.overline{text-decoration:overline}
.line-through{text-decoration:line-through}
.aqua{color:#00bfbf}
.aqua-background{background-color:#00fafa}
.black{color:#000}
.black-background{background-color:#000}
.blue{color:#0000bf}
.blue-background{background-color:#0000fa}
.fuchsia{color:#bf00bf}
.fuchsia-background{background-color:#fa00fa}
.gray{color:#606060}
.gray-background{background-color:#7d7d7d}
.green{color:#006000}
.green-background{background-color:#007d00}
.lime{color:#00bf00}
.lime-background{background-color:#00fa00}
.maroon{color:#600000}
.maroon-background{background-color:#7d0000}
.navy{color:#000060}
.navy-background{background-color:#00007d}
.olive{color:#606000}
.olive-background{background-color:#7d7d00}
.purple{color:#600060}
.purple-background{background-color:#7d007d}
.red{color:#bf0000}
.red-background{background-color:#fa0000}
.silver{color:#909090}
.silver-background{background-color:#bcbcbc}
.teal{color:#006060}
.teal-background{background-color:#007d7d}
.white{color:#bfbfbf}
.white-background{background-color:#fafafa}
.yellow{color:#bfbf00}
.yellow-background{background-color:#fafa00}
span.icon>.fa{cursor:default}
.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c}
.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900}
.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400}
.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000}
.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
.conum[data-value] *{color:#fff!important}
.conum[data-value]+b{display:none}
.conum[data-value]:after{content:attr(data-value)}
pre .conum[data-value]{position:relative;top:-.125em}
b.conum *{color:inherit!important}
.conum:not([data-value]):empty{display:none}
h1,h2{letter-spacing:-.01em}
dt,th.tableblock,td.content{text-rendering:optimizeLegibility}
p,td.content{letter-spacing:-.01em}
p strong,td.content strong{letter-spacing:-.005em}
p,blockquote,dt,td.content{font-size:1.0625rem}
p{margin-bottom:1.25rem}
.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
.print-only{display:none!important}
@media print{@page{margin:1.25cm .75cm}
*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
a{color:inherit!important;text-decoration:underline!important}
a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
abbr[title]:after{content:" (" attr(title) ")"}
pre,blockquote,tr,img{page-break-inside:avoid}
thead{display:table-header-group}
img{max-width:100%!important}
p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
#toc,.sidebarblock,.exampleblock>.content{background:none!important}
#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
.sect1{padding-bottom:0!important}
.sect1+.sect1{border:0!important}
#header>h1:first-child{margin-top:1.25rem}
body.book #header{text-align:center}
body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0}
body.book #header .details{border:0!important;display:block;padding:0!important}
body.book #header .details span:first-child{margin-left:0!important}
body.book #header .details br{display:block}
body.book #header .details br+span:before{content:none!important}
body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
.listingblock code[data-lang]:before{display:block}
#footer{background:none!important;padding:0 .9375em}
#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em}
.hide-on-print{display:none!important}
.print-only{display:block!important}
.hide-for-print{display:none!important}
.show-for-print{display:inherit!important}}
</style>
</head>
<body class="article">
<div id="header">
<h1>Raspberry Pi camera/LED demo</h1>
</div>
<div id="content">
<div class="sect1">
<h2 id="_overview">Overview</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The <a href="/">demo</a> consists of web app providing access to a webcam and a LED attached to a RaspberryPi.
The device is assumed to have a limited bandwidth towards the server hosting the web app.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_objective">Objective</h2>
<div class="sectionbody">
<div class="paragraph">
<p>The demo shows how to use websockets to communicate bidirectionally with an embedded device using standard protocols.</p>
</div>
<div class="paragraph">
<p>It also shows that it&#8217;s possible to use Smart.c to develop also the cloud endpoint and expose WebSocket and RESTful APIs
easy to integreate with modern web stacks.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_how_it_works">How it works</h2>
<div class="sectionbody">
<div class="imageblock">
<div class="content">
<img src="docs/arch.png" alt="arch">
</div>
</div>
<div class="paragraph">
<p>There are two components, once with runs on the device (<code>device_side</code>) and one that runs on a stronger machine
and with more bandwidth (<code>cloud_side</code>).</p>
</div>
<div class="paragraph">
<p>The device app connects to the cloud app via websocket and sends a new jpeg frame as fast as the underlying <code>raspistill</code> camera
grabbing application can handle. The device automatically attempts reconnecting.</p>
</div>
<div class="paragraph">
<p>The cloud side serves the webapp static pages and serves an MPJEG image on <code>/mpjg</code>.
The MPJEG image handler blocks all the clients until a JPEG frame arrives via websocket
and then every client will recieve a copy of the frame.</p>
</div>
<div class="paragraph">
<p>The web app can turn on and off the LED via a RESTful api accessible via the <code>/api</code> handler.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_installation">Installation</h2>
<div class="sectionbody">
<div class="sect2">
<h3 id="_server_side">Server side</h3>
<div class="listingblock">
<div class="content">
<pre>git clone https://github.com/cesanta/mongoose
cd mongoose/examples/web_demo
make cloud_side &amp;&amp; ./cloud_side 0.0.0.0:8080</pre>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_raspberry_pi">Raspberry Pi</h3>
<div class="paragraph">
<p>The instructions provided here are tailored for the Raspbian distribution.</p>
</div>
<div class="sect3">
<h4 id="_dependencies">Dependencies</h4>
<div class="dlist">
<dl>
<dt class="hdlist1">jpegoptim</dt>
<dd>
<p>apt-get install jpegoptim</p>
</dd>
<dt class="hdlist1">camera</dt>
<dd>
<p>run raspi-config and enable camera</p>
</dd>
</dl>
</div>
</div>
<div class="sect3">
<h4 id="_led">LED</h4>
<div class="paragraph">
<p>In order to access the led on your <a href="http://www.qdh.org.uk/wordpress/?page_id=15">HotPi</a>
board you need to export the gpio pins:</p>
</div>
<div class="listingblock">
<div class="content">
<pre>for i in 22 23 24; do
echo $i &gt;/sys/class/gpio/export
echo out &gt;/sys/class/gpio/gpio$i/direction
chgrp pi /sys/class/gpio/gpio$i/value
done</pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_build_and_run">Build and run</h4>
<div class="listingblock">
<div class="content">
<pre>git clone https://github.com/cesanta/mongoose
cd mongoose/examples/web_demo
make device_side &amp;&amp; ./device_side yourserver:8080</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2014-11-03 21:30:06 GMT
</div>
</div>
</body>
</html>
@@ -0,0 +1 @@
../../docs/arch.png
+15
View File
@@ -0,0 +1,15 @@
/*
* Framework7 0.9.6
* Full Featured HTML Framework For Building iOS 7 Apps
*
* http://www.idangero.us/framework7
*
* Copyright 2014, Vladimir Kharlampidi
* The iDangero.us
* http://www.idangero.us/
*
* Licensed under MIT
*
* Released on: September 13, 2014
*/
html,body{position:relative;height:100%;width:100%;overflow-x:hidden}body{font-family:Helvetica Neue,Helvetica,Arial,sans-serif;margin:0;padding:0;color:#000;font-size:14px;line-height:1.4;width:100%;-webkit-text-size-adjust:100%;background:#fff;overflow:hidden}@media all and (width:1024px) and (height:691px) and (orientation:landscape){html,body{height:671px}}@media all and (width:1024px) and (height:692px) and (orientation:landscape){html,body{height:672px}}*{-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-touch-callout:none}a,input,textarea,select{outline:0}a{text-decoration:none;color:#007aff}p{margin:1em 0}.row{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;-webkit-box-lines:multiple;-moz-box-lines:multiple;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:start;-ms-flex-align:start;-webkit-align-items:flex-start;align-items:flex-start}.row>[class*=col-]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row .col-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .col-100{width:100%}.row .col-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .col-95{width:95%}.row .col-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .col-90{width:90%}.row .col-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .col-85{width:85%}.row .col-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .col-80{width:80%}.row .col-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .col-75{width:75%}.row .col-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100% - 15px*0.5000000000000002) / 1.5000000000000002)}.row.no-gutter .col-66{width:66.66666666666666%}.row .col-60{width:60%;width:-webkit-calc((100% - 15px*0.6666666666666667) / 1.6666666666666667);width:calc((100% - 15px*0.6666666666666667) / 1.6666666666666667)}.row.no-gutter .col-60{width:60%}.row .col-50{width:50%;width:-webkit-calc((100% - 15px*1) / 2);width:calc((100% - 15px*1) / 2)}.row.no-gutter .col-50{width:50%}.row .col-40{width:40%;width:-webkit-calc((100% - 15px*1.5) / 2.5);width:calc((100% - 15px*1.5) / 2.5)}.row.no-gutter .col-40{width:40%}.row .col-33{width:33.333333333333336%;width:-webkit-calc((100% - 15px*2) / 3);width:calc((100% - 15px*2) / 3)}.row.no-gutter .col-33{width:33.333333333333336%}.row .col-25{width:25%;width:-webkit-calc((100% - 15px*3) / 4);width:calc((100% - 15px*3) / 4)}.row.no-gutter .col-25{width:25%}.row .col-20{width:20%;width:-webkit-calc((100% - 15px*4) / 5);width:calc((100% - 15px*4) / 5)}.row.no-gutter .col-20{width:20%}.row .col-15{width:15%;width:-webkit-calc((100% - 15px*5.666666666666667) / 6.666666666666667);width:calc((100% - 15px*5.666666666666667) / 6.666666666666667)}.row.no-gutter .col-15{width:15%}.row .col-10{width:10%;width:-webkit-calc((100% - 15px*9) / 10);width:calc((100% - 15px*9) / 10)}.row.no-gutter .col-10{width:10%}.row .col-5{width:5%;width:-webkit-calc((100% - 15px*19) / 20);width:calc((100% - 15px*19) / 20)}.row.no-gutter .col-5{width:5%}@media all and (min-width:768px){.row .tablet-100{width:100%;width:-webkit-calc((100% - 15px*0) / 1);width:calc((100% - 15px*0) / 1)}.row.no-gutter .tablet-100{width:100%}.row .tablet-95{width:95%;width:-webkit-calc((100% - 15px*0.05263157894736836) / 1.0526315789473684);width:calc((100% - 15px*0.05263157894736836) / 1.0526315789473684)}.row.no-gutter .tablet-95{width:95%}.row .tablet-90{width:90%;width:-webkit-calc((100% - 15px*0.11111111111111116) / 1.1111111111111112);width:calc((100% - 15px*0.11111111111111116) / 1.1111111111111112)}.row.no-gutter .tablet-90{width:90%}.row .tablet-85{width:85%;width:-webkit-calc((100% - 15px*0.17647058823529416) / 1.1764705882352942);width:calc((100% - 15px*0.17647058823529416) / 1.1764705882352942)}.row.no-gutter .tablet-85{width:85%}.row .tablet-80{width:80%;width:-webkit-calc((100% - 15px*0.25) / 1.25);width:calc((100% - 15px*0.25) / 1.25)}.row.no-gutter .tablet-80{width:80%}.row .tablet-75{width:75%;width:-webkit-calc((100% - 15px*0.33333333333333326) / 1.3333333333333333);width:calc((100% - 15px*0.33333333333333326) / 1.3333333333333333)}.row.no-gutter .tablet-75{width:75%}.row .tablet-66{width:66.66666666666666%;width:-webkit-calc((100% - 15px*0.5000000000000002) / 1.5000000000000002);width:calc((100Line truncated
+18
View File
@@ -0,0 +1,18 @@
/*
* Framework7 0.9.6
* Full Featured HTML Framework For Building iOS 7 Apps
*
* http://www.idangero.us/framework7
*
* Copyright 2014, Vladimir Kharlampidi
* The iDangero.us
* http://www.idangero.us/
*
* Licensed under MIT
*
* Released on: September 13, 2014
*/
!function(){"use strict";window.Framework7=function(b){function c(a){var b=a.replace(/^./,function(a){return a.toUpperCase()});l["onPage"+b]=function(b,c){return l.onPage(a,b,c)}}function d(a,b,c){b=n(b);var d=b.find(a);return d.length>1&&("string"==typeof c.selector&&(d=b.find(c.selector+" "+a)),d.length>1&&(d=b.find("."+l.params.viewMainClass+" "+a))),1===d.length?d:void 0}function e(a,b,c){"to-left"===c&&(a.removeClass("page-on-center").addClass("page-from-center-to-left"),b.addClass("page-from-right-to-center")),"to-right"===c&&(a.removeClass("page-on-left").addClass("page-from-left-to-center"),b.removeClass("page-on-center").addClass("page-from-center-to-right"))}function f(a,b,c){"to-left"===c&&(b.removeClass("navbar-on-right").addClass("navbar-from-right-to-center"),b.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),a.removeClass("navbar-on-center").addClass("navbar-from-center-to-left"),a.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&(a.hasClass("center")&&b.find(".sliding.left .back .icon").length>0&&(this.f7NavbarLeftOffset+=b.find(".sliding.left .back span")[0].offsetLeft),a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarLeftOffset+"px,0,0)")),a.transform("translate3d("+this.f7NavbarLeftOffset+"px,0,0)")})),"to-right"===c&&(a.removeClass("navbar-on-left").addClass("navbar-from-left-to-center"),a.find(".sliding").each(function(){var a=n(this);a.transform("translate3d(0px,0,0)"),l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d(0px,0,0)")}),b.removeClass("navbar-on-center").addClass("navbar-from-center-to-right"),b.find(".sliding").each(function(){var a=n(this);l.params.animateNavBackIcon&&a.hasClass("left")&&a.find(".back .icon").length>0&&a.find(".back .icon").transform("translate3d("+-this.f7NavbarRightOffset+"px,0,0)"),a.transform("translate3d("+this.f7NavbarRightOffset+"px,0,0)")}))}function g(a,b,c,e){var f,g,h,i,j,k,m,o,p=n(a.container),q=n(a.pagesContainer);if(l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var r=0;r<c.length;r++)n(l._tempDomElement).append(c[r]);else n(l._tempDomElement).append(c);if(o=e.reloadPrevious?"left":"center",f=d(".page",l._tempDomElement,a),!f)return void(a.allowPageChange=!0);if(f.addClass("page-on-"+o),h=q.children(".page:not(.cached)"),e.reloadPrevious&&1===h.length)return void(a.allowPageChange=!0);g=h.eq(h.length-1),a.params.dynamicNavbar&&(m=!0,j=d(".navbar-inner",l._tempDomElement,a),j||(m=!1),k=p.find(".navbar"),i=k.find(".navbar-inner:last-child")),m&&j.addClass("navbar-on-"+o),b||(b="#content-"+a.history.length,a.params.domCache||(a.contentCache[b]=c)),a.url=b;var s=a.history[a.history.length-(e.reloadPrevious?2:1)];0===s.indexOf("#content")&&s in a.contentCache&&(a.contentCache[s]=null,delete a.contentCache[s]),a.history[a.history.length-(e.reloadPrevious?2:1)]=b,e.reloadPrevious?(g=g.prev(".page"),f.insertBefore(g),m&&(i=i.prev(".navbar-inner"),j.insertAfter(i))):(q.append(f[0]),m&&k.append(j[0])),l.pageRemoveCallback(a,g[0],o),g.remove(),m&&i.remove(),l.pageInitCallback(a,f[0],b,o,m?j[0]:void 0),m&&l.navbarInitCallback(a,f[0],k[0],j[0],b,o),a.allowPageChange=!0}function h(a,b,c,g){function h(){a.allowPageChange=!0,i.removeClass("page-from-right-to-center page-on-right").addClass("page-on-center"),j.removeClass("page-from-center-to-left page-on-center").addClass("page-on-left"),r&&(p.removeClass("navbar-from-right-to-center navbar-on-right").addClass("navbar-on-center"),o.removeClass("navbar-from-center-to-left navbar-on-center").addClass("navbar-on-left")),l.pageAnimCallbacks("after",a,{pageContainer:i[0],url:b,position:"right",oldPage:j,newPage:i}),l.params.pushState&&l.pushStateClearQueue(),a.params.swipeBackPage||a.params.preloadPreviousPage||(a.params.domCache?(j.addClass("cached"),o.addClass("cached")):(j.remove(),o.remove())),a.params.uniqueHistory&&w&&a.refreshPreviousPage()}var i,j,k,m,o,p,q,r,s=n(a.container),t=n(a.pagesContainer),u=g.animatePages;if("undefined"==typeof u&&(u=a.params.animatePages),l.pluginHook("loadPage",a,b,c),l._tempDomElement.innerHTML="",b||"string"==typeof c)l._tempDomElement.innerHTML=c;else if("length"in c&&c.length>1)for(var v=0;v<c.length;v++)n(l._tempDomElement).append(c[v]);else n(l._tempDomElement).append(c);if(i=d(".page",l._tempDomElement,a),!i)return void(a.allowPageChange=!0);if(i.addClass("page-on-right"),k=t.children(".page:not(.cached)"),k.length>1){for(m=0;m<k.length-2;m++)a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).remove());a.params.domCache?n(k[m]).addClass("cached"):(l.pageRemoveCallback(a,k[m],"left"),n(k[m]).removeLine truncated
var e=a.title?'<div class="modal-title">'+a.title+"</div>":"",f=a.title?'<div class="modal-text">'+a.text+"</div>":"",g=a.afterText?a.afterText:"",h=a.buttons&&0!==a.buttons.length?"":"modal-no-buttons";b='<div class="modal '+h+'"><div class="modal-inner">'+(e+f+g)+'</div><div class="modal-buttons">'+c+"</div></div>"}s.innerHTML=b;var i=n(s).children();return n("body").append(i[0]),i.find(".modal-button").each(function(b,c){n(c).on("click",function(c){a.buttons[b].close!==!1&&l.closeModal(i),a.buttons[b].onClick&&a.buttons[b].onClick(i,c),a.onClick&&a.onClick(i,b)})}),l.openModal(i),i[0]},l.alert=function(a,b,c){return"function"==typeof b&&(c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.confirm=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,buttons:[{text:l.params.modalButtonCancel,onClick:d},{text:l.params.modalButtonOk,bold:!0,onClick:c}]})},l.prompt=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){0===b&&d&&d(n(a).find(".modal-text-input").val()),1===b&&c&&c(n(a).find(".modal-text-input").val())}})},l.modalLogin=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="text" name="modal-username" placeholder="'+l.params.modalUsernamePlaceholder+'" class="modal-text-input modal-text-input-double"><input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input modal-text-input-double">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-username"]').val(),f=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e,f),1===b&&c&&c(e,f)}})},l.modalPassword=function(a,b,c,d){return"function"==typeof b&&(d=arguments[2],c=arguments[1],b=void 0),l.modal({text:a||"",title:"undefined"==typeof b?l.params.modalTitle:b,afterText:'<input type="password" name="modal-password" placeholder="'+l.params.modalPasswordPlaceholder+'" class="modal-text-input">',buttons:[{text:l.params.modalButtonCancel},{text:l.params.modalButtonOk,bold:!0}],onClick:function(a,b){var e=n(a).find('.modal-text-input[name="modal-password"]').val();0===b&&d&&d(e),1===b&&c&&c(e)}})},l.showPreloader=function(a){return l.modal({title:a||l.params.modalPreloaderTitle,text:'<div class="preloader"></div>'})},l.hidePreloader=function(){l.closeModal(".modal.modal-in")},l.showIndicator=function(){n("body").append('<div class="preloader-indicator-overlay"></div><div class="preloader-indicator-modal"><span class="preloader preloader-white"></span></div>')},l.hideIndicator=function(){n(".preloader-indicator-overlay, .preloader-indicator-modal").remove()},l.actions=function(a,b){var c,d,e,f=!1;1===arguments.length?b=a:l.device.ios?l.device.ipad&&(f=!0):n(window).width()>=768&&(f=!0),b=b||[],b.length>0&&!n.isArray(b[0])&&(b=[b]);var g;if(f){var h='<div class="popover actions-popover"><div class="popover-inner">{{#each this}}<div class="list-block"><ul>{{#each this}}{{#if label}}<li class="actions-popover-label {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</li>{{else}}<li><a href="#" class="item-link list-button {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}">{{text}}</a></li>{{/if}}{{/each}}</ul></div>{{/each}}</div></div>';l._compiledTemplates.actionsPopover||(l._compiledTemplates.actionsPopover=o.compile(h));var i=l._compiledTemplates.actionsPopover(b);c=n(l.popover(i,a,!0)),d=".list-block ul",e=".list-button"}else{if(l.params.modalActionsTemplate)l._compiledTemplates.actions||(l._compiledTemplates.actions=o.compile(l.params.modalActionsTemplate)),g=l._compiledTemplates.actions(b);else{for(var j="",k=0;k<b.length;k++)for(var m=0;m<b[k].length;m++){0===m&&(j+='<div class="actions-modal-group">');var p=b[k][m],q=p.label?"actions-modal-label":"actions-modal-button";p.bold&&(q+=" actions-modal-button-bold"),p.color&&(q+=" color-"+p.color),j+='<span class="'+q+'">'+p.text+"</span>",m===b[k].length-1&&(j+="</div>")}g='<div class="actions-modal">'+j+"</div>"}s.innerHTML=g,c=n(s).children(),n("body").append(c[0]),d=".actions-modal-group",e=".actions-modal-button"}var r=c.find(d);return r.each(function(a,d){var g=a;n(d).children().each(function(a,d){var h,i=a,j=b[g][i];!f&&n(d).is(e)&&(h=n(d)),f&&n(d).find(e).length>0&&(h=n(d).find(e)),h&&h.on("click",function(a){j.close!==!1&&l.closeModal(c),j.onClick&&j.onClick(c,a)})})}),f||l.oLine truncated
}function b(){var a=B.parents(".page-content, .panel");return 0===a.length?!1:("yes"!==a.prop("scrollHandlerSet")&&(a.on("scroll",function(){clearTimeout(C)}),a.prop("scrollHandlerSet","yes")),!0)}function c(){B.addClass("active-state")}function d(){B.removeClass("active-state")}function e(a){var b="button checkbox file image radio submit input textarea".split(" ");return document.activeElement&&a!==document.activeElement&&document.activeElement!==document.body?b.indexOf(a.nodeName.toLowerCase())>=0?!1:!0:!1}function f(a){return"input"===a.nodeName.toLowerCase()&&"file"===a.type?!1:a.className.indexOf("no-fastclick")>=0?!1:!0}function g(a){if(document.activeElement===a)return!1;var b=a.nodeName.toLowerCase(),c="button checkbox file image radio submit".split(" ");return a.disabled||a.readOnly?!1:"textarea"===b?!0:"select"===b?"android"===l.device.os?!1:!0:"input"===b&&c.indexOf(a.type)<0?!0:void 0}function h(a){if(a=n(a),a.is("label")||a.parents("label").length>0){if("android"===l.device.os){var b=l.device.osVersion.split(".");return 1*b[0]>4||1*b[0]===4&&1*b[1]>=4?!1:!0}return!1}return!0}function i(b){a(b).addClass("active-state"),"which"in b&&3===b.which&&setTimeout(function(){n(".active-state").removeClass("active-state")},0)}function j(){n(".active-state").removeClass("active-state")}function k(){n(".active-state").removeClass("active-state")}function m(d){if(A=!1,d.targetTouches.length>1)return!0;if(D=f(d.target),!D)return w=!1,!0;if("ios"===l.device.os){var g=window.getSelection();if(g.rangeCount&&(!g.isCollapsed||document.activeElement===g.focusNode))return x=!0,!0;x=!1}"android"===l.device.os&&e(d.target)&&document.activeElement.blur(),w=!0,v=d.target,u=(new Date).getTime(),s=d.targetTouches[0].pageX,t=d.targetTouches[0].pageY,"ios"===l.device.os&&(y=void 0,n(v).parents().each(function(){var a=this;a.scrollHeight>a.offsetHeight&&!y&&(y=a,y.f7ScrollTop=y.scrollTop)})),d.timeStamp-z<200&&d.preventDefault(),l.params.activeState&&(B=a(d),b(d)?C=setTimeout(c,80):c())}function o(a){if(w){var b=!1,c=l.params.fastClicksDistanceThreshold;if(c){var e=a.targetTouches[0].pageX,f=a.targetTouches[0].pageY;(Math.abs(e-s)>c||Math.abs(f-t)>c)&&(b=!0)}else b=!0;b&&(w=!1,v=null,A=!0),l.params.activeState&&(clearTimeout(C),d())}}function p(a){if(clearTimeout(C),!w)return!x&&D&&a.preventDefault(),!0;if(document.activeElement===a.target)return!0;if(x||a.preventDefault(),a.timeStamp-z<200)return!0;if(z=a.timeStamp,u=0,w=!1,"ios"===l.device.os&&y&&y.scrollTop!==y.f7ScrollTop)return!1;l.params.activeState&&(c(),setTimeout(d,0)),g(v)&&v.focus(),a.preventDefault();var b=a.changedTouches[0],e=document.createEvent("MouseEvents"),f="click";return"android"===l.device.os&&"select"===v.nodeName.toLowerCase()&&(f="mousedown"),e.initMouseEvent(f,!0,!0,window,1,b.screenX,b.screenY,b.clientX,b.clientY,!1,!1,!1,!1,0,null),e.forwardedTouchEvent=!0,v.dispatchEvent(e),!1}function q(){w=!1,v=null}function r(a){var b=!1;return w?(v=null,w=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(v||(b=!0),document.activeElement===v&&(b=!0),a.forwardedTouchEvent&&(b=!0),a.cancelable||(b=!0),b||(a.stopImmediatePropagation(),a.stopPropagation(),v?(h(v)||A)&&a.preventDefault():a.preventDefault(),v=null),b)}l.params.activeState&&n("html").addClass("watch-active-state");var s,t,u,v,w,x,y,z,A,B,C,D;l.support.touch?(document.addEventListener("click",r,!0),l.addGlobalEventListener("touchstart",m),l.addGlobalEventListener("touchmove",o),l.addGlobalEventListener("touchend",p),l.addGlobalEventListener("touchcancel",q)):l.params.activeState&&(l.addGlobalEventListener("mousedown",i),l.addGlobalEventListener("mousemove",j),l.addGlobalEventListener("mouseup",k))},l.initClickEvents=function(){function a(a){function b(a){return"false"===a?!1:"true"===a?!0:void 0}var c=n(this),d=c.attr("href"),e="a"===c[0].nodeName.toLowerCase();if(e)for(var f=0;f<l.params.externalLinks.length;f++){if(c.hasClass(l.params.externalLinks[f]))return;if(c[0].rel===l.params.externalLinks[f])return}if(c.hasClass("smart-select")&&l.smartSelectOpen&&l.smartSelectOpen(c),c.hasClass("open-panel")&&l.openPanel(1===n(".panel").length?n(".panel").hasClass("panel-left")?"left":"right":"right"===c.attr("data-panel")?"right":"left"),c.hasClass("close-panel")&&l.closePanel(),c.hasClass("panel-overlay")&&l.params.panelsCloseByOutside&&l.closePanel(),c.hasClass("open-popover")){var g;g=c.attr("data-popover")?c.attr("data-popover"):".popover",l.popover(g,c)}c.hasClass("close-popover")&&l.closeModal(".popover.modal-in");var h;c.hasClass("open-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup",l.popup(h)),c.hasClass("close-popup")&&(h=c.attr("data-popup")?c.attr("data-popup"):".popup.modal-in",l.closeModal(h));var i;if(c.hasClass("open-login-screen")&&(i=c.attr("data-login-screen")?c.attr("data-login-screen"):".login-screen",l.loginScreen(i)),c.hasClass("close-login-screen")&&l.closeModal(".login-screen.modal-in"),c.hasClass("modal-overlay")&&(n(".modal.modal-in").length>0&&l.params.modalCloseByLine truncated
l.addNotification=function(a){if(a){"undefined"==typeof a.media&&(a.media=l.params.notificationMedia),"undefined"==typeof a.title&&(a.title=l.params.notificationTitle),"undefined"==typeof a.subtitle&&(a.subtitle=l.params.notificationSubtitle),"undefined"==typeof a.closeIcon&&(a.closeIcon=l.params.notificationCloseIcon),"undefined"==typeof a.hold&&(a.hold=l.params.notificationHold),"undefined"==typeof a.closeOnClick&&(a.closeOnClick=l.params.notificationCloseOnClick),v||(v=document.createElement("div"));var b=n(".notifications");0===b.length&&(n("body").append('<div class="notifications list-block media-list"><ul></ul></div>'),b=n(".notifications"));var c,d=b.children("ul");c=a.custom?"<li>"+a.custom+"</li>":'<li class="notification-item notification-hidden"><div class="item-content">'+(a.media?'<div class="item-media">'+a.media+"</div>":"")+'<div class="item-inner"><div class="item-title-row">'+(a.title?'<div class="item-title">'+a.title+"</div>":"")+(a.closeIcon?'<div class="item-after"><a href="#" class="close-notification"><span></span></a></div>':"")+"</div>"+(a.subtitle?'<div class="item-subtitle">'+a.subtitle+"</div>":"")+(a.message?'<div class="item-text">'+a.message+"</div>":"")+"</div></div></li>",v.innerHTML=c;var e=n(v).children();e.on("click",function(b){var c=!1;n(b.target).is(".close-notification")||n(b.target).parents(".close-notification").length>0?c=!0:(a.onClick&&a.onClick(b,e[0]),a.closeOnClick&&(c=!0)),c&&l.closeNotification(e[0])}),a.onClose&&e.data("f7NotificationOnClose",function(){a.onClose(e[0])}),a.additionalClass&&e.addClass(a.additionalClass),a.hold&&setTimeout(function(){e.length>0&&l.closeNotification(e[0])},a.hold),d.prepend(e[0]),b.show();var f=e.height();e.css("marginTop",-f+"px"),e.transition(0);{e[0].clientLeft}return e.transition(""),e.css("marginTop","0px"),b.transform("translate3d(0, 0,0)"),e.removeClass("notification-hidden"),e[0]}},l.closeNotification=function(a){if(a=n(a),0!==a.length&&!a.hasClass("notification-item-removing")){var b=n(".notifications"),c=a.height();a.css("height",c+"px").transition(0);{a[0].clientLeft}a.css("height","0px").transition("").addClass("notification-item-removing"),a.data("f7NotificationOnClose")&&a.data("f7NotificationOnClose")(),0===b.find(".notification-item:not(.notification-item-removing)").length&&b.transform(""),a.addClass("notification-hidden").transitionEnd(function(){a.remove(),0===b.find(".notification-item").length&&b.hide()})}};var w=[];return l.initPlugins=function(){for(var a in l.plugins){var b=l.plugins[a](l,l.params[a]);b&&w.push(b)}},l.pluginHook=function(a){for(var b=0;b<w.length;b++)w[b].hooks&&a in w[b].hooks&&w[b].hooks[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])},l.pluginPrevent=function(a){for(var b=!1,c=0;c<w.length;c++)w[c].prevents&&a in w[c].prevents&&w[c].prevents[a](arguments[1],arguments[2],arguments[3],arguments[4],arguments[5])&&(b=!0);return b},l.pluginProcess=function(a,b){for(var c=b,d=0;d<w.length;d++)w[d].preprocess&&process in w[d].preprocess&&(c=w[d].preprocess[process](b,arguments[2],arguments[3],arguments[4],arguments[5],arguments[6]));return c},l.init=function(){l.initPlugins&&l.initPlugins(),l.getDeviceInfo&&l.getDeviceInfo(),l.initFastClicks&&l.params.fastClicks&&l.initFastClicks(),l.initClickEvents&&l.initClickEvents(),n(".page").each(function(){var a=n(this),b=a.parents("."+l.params.viewClass);if(0!==b.length){var c=b[0].f7View||!1,d=c&&c.url?c.url:!1;b&&b.attr("data-page",a.attr("data-page")||void 0),l.pageInitCallback(c,this,d,"center")}}),l.initResize&&l.initResize(),l.initPushState&&l.params.pushState&&l.initPushState(),l.initSwipeout&&l.params.swipeout&&l.initSwipeout(),l.initSortable&&l.params.sortable&&l.initSortable(),l.initSwipePanels&&l.params.swipePanel&&l.initSwipePanels(),l.params.onAppInit&&l.params.onAppInit(),l.pluginHook("appInit")},l.params.init&&l.init(),l};var a=function(){var a=function(a){var b=this,c=0;for(c=0;c<a.length;c++)b[c]=a[c];return b.length=a.length,this},b=function(b,c){var d=[],e=0;if(b&&!c&&b instanceof a)return b;if(b)if("string"==typeof b){var f=(c||document).querySelectorAll(b);for(e=0;e<f.length;e++)d.push(f[e])}else if(b.nodeType||b===window||b===document)d.push(b);else if(b.length>0&&b[0].nodeType)for(e=0;e<b.length;e++)d.push(b[e]);return new a(d)};a.prototype={addClass:function(a){if("undefined"==typeof a)return this;for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.add(b[c]);return this},removeClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.remove(b[c]);return this},hasClass:function(a){return this[0]?this[0].classList.contains(a):!1},toggleClass:function(a){for(var b=a.split(" "),c=0;c<b.length;c++)for(var d=0;d<this.length;d++)this[d].classList.toggle(b[c]);return this},attr:function(a,b){if("undefined"==typeof b)return this[0]?this[0].getAttribute(a):void 0;for(var c=0;c<this.length;c++)this[c].setAttribute(a,b);return tLine truncated
@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>
<head>
<!-- Required meta tags-->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="stylesheet" href="framework7.min.css">
<title>Smart.c mjpg example</title>
<style type="text/css">
.image img {
display: block;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<!-- Status bar overlay for full screen mode (PhoneGap) -->
<div class="statusbar-overlay"></div>
<!-- Views -->
<div class="views">
<!-- Your main view, should have "view-main" class -->
<div class="view view-main">
<!-- Top Navbar-->
<div class="navbar">
<div class="navbar-inner">
<!-- We need cool sliding animation on title element, so we have additional "sliding" class -->
<div class="left">
<a href="docs/docs/doc.html" class="link" onclick="location='docs/docs/doc.html'">
<span>About</span>
</a>
</div>
<div class="center sliding">Remote Camera</div>
<div class="right sliding">
<a href="https://github.com/cesanta/mongoose/tree/master/examples/raspberry_pi_mjpeg_led" class="link" onclick="location='https://github.com/cesanta/mongoose/tree/master/examples/raspberry_pi_mjpeg_led'">
<span>Github</span>
<i class="icon icon-next"></i>
</a>
</div>
</div>
</div>
<!-- Pages container, because we use fixed-through navbar and toolbar, it has additional appropriate classes-->
<div class="pages navbar-through toolbar-through">
<!-- Page, "data-page" contains page name -->
<div data-page="index" class="page">
<!-- Scrollable page content -->
<div class="page-content">
<div class="content-block-title">Camera View</div>
<div class="content-block image">
<img src="/mjpg">
</div>
<div class="content-block-title">Device Control</div>
<div class="list-block">
<form action="/api" method="GET" enctype="application/json"
id="form-control">
<ul>
<!-- Switch (Checkbox) -->
<li>
<div class="item-content">
<div class="item-inner">
<div class="item-title label">LED on/off</div>
<div class="item-input">
<label class="label-switch">
<input type="checkbox" name="onoff">
<div class="checkbox"></div>
</label>
</div>
</div>
</div>
</li>
</ul>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="framework7.min.js"></script>
<script type="text/javascript">
var myApp = new Framework7({
pushState: true,
swipePanel: 'left',
// ... other parameters
});
Dom7(document).on('change', '#form-control', function(ev) {
var data = myApp.formToJSON('#form-control');
var json = JSON.stringify(data);
Dom7.ajax({
url: '/api',
method: 'POST',
contentType: 'application/json',
data: json
});
});
</script>
</body>
</html>