Syma X5SW のFPVカメラをPCから見る

ドローン X5SW のFPV Cameraは公式iPhone/Androidアプリから見ることができますが、工夫するとPCからも閲覧できます。その方法は

* PCでドローンにWiFi接続
* http://192.168.1.1/request_av.cgi にアクセス
* `var id=40463458;` の部分の数字を記憶する。
* http://192.168.1.1/Videostream.cgi?User=admin&pwd=&id=40463458 先ほど記憶したIDと同じIDをURLに入れてアクセスする
    * MotionJPEGで降ってくる。Firefoxでは直に見れた。Chromeではただのjpegとされるらしく無理だった。
* **Digest認証を求められたときはユーザー名 `admin` でパスワードは空白**

# 表示する
これを手動でやるのは面倒なので、JSONP(もどき)とiframeでSame origin policyを回避しつつ表示できるようにした。以下のHTMLをローカルに保存してFirefoxとかで開くと、リアルタイム中継が見られる。


```html
<!DOCTYPE html>
<body onLoad="start()">
<script src="http://192.168.1.1/request_av.cgi"></script>
<script>
function start(){
console.log(id);
document.getElementById("fpv").src = "http://192.168.1.1/Videostream.cgi?User=admin&pwd=&id="+id;
}
</script>
<iframe src="" id="fpv" width="640" height="480" style="border:none;overflow:hidden;" scrolling="no"></iframe>
</body>
```

![Screenshot from 2015-12-28 13:54:39.png](https://qiita-image-store.s3.amazonaws.com/0/19148/a71c12da-a2d7-5a46-132f-cb788e7bd939.png)

複数接続はできるが、つなげばつなぐほどきっと重くなる。
Digest認証を求められたときはユーザー名 `admin` でパスワードは空白にする。

# 表示をリレーする

```py3
from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading
import urllib.request # high-level http client
import re
import http.client # low-level http client for continuous stream

BUFSZ = 1024
HOST="192.168.1.1"
PORT="80"
url_reqav = "http://{0}:{1}/request_av.cgi?User=admin&pwd=".format(HOST,PORT)
url_reqvs = "/Videostream.cgi?User=admin&pwd=&id={0}"
id_pat = re.compile("\d+")


# 1対1 ブラウザ <=> ドローン
# 接続の例
class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        with urllib.request.urlopen(url=url_reqav) as resp:
            id_line = resp.readlines()[1].decode("utf8")
            self.id_num = id_pat.search(id_line).group(0)
 
            self.send_response(200)
            self.send_header('Content-type', 'multipart/x-mixed-replace;boundary=ipcamera')
            self.end_headers()

            buf = bytearray(BUFSZ)

            con = http.client.HTTPConnection("{0}:{1}".format(HOST,PORT))
            con.request("GET",url_reqvs.format(self.id_num))
            resp = con.getresponse()

            while not resp.closed:
                buf = resp.read(BUFSZ)
                self.wfile.write(buf)

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('localhost', 8080), Handler)
    print ('Starting server, use <Ctrl-C> to stop')
    server.serve_forever()
```

ホントは中継ついでに保存もしたい。そのためには環状バッファを実装する必要がある気がする。

![Screenshot from 2015-12-28 21:58:48.png](https://qiita-image-store.s3.amazonaws.com/0/19148/ea90aa9d-444d-bbaf-56d1-14432418fb28.png)


# 追記
Pythonを使わず、wgetとオリジナルのC言語製 MJPEG スプリッターを使うと、VLCでリアルタイムで見ながら保存もできるようになります。以下参照。
`cc -O3 separate.c` でコンパイルして、 `bash syma.sh` を実行。
デバッグ不足で一度目は映らなかったりするので閉じて再度トライのこと。
https://gist.github.com/keiya/cb9e2901ccc1e813c634

```c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_FRAME_SIZE 256*1024 // KB
#define CONTENT_LENGTH "Content-Length: "
#define PAYLOAD "\r\n"

int main()
{
    char buf[MAX_FRAME_SIZE];
    //size_t rs = fread(buf, MAX_FRAME_SIZE, 1, stdin);
    // parse content-length
    int length;
    int seq = 0;
    while (1) {
        while( fgets(buf, MAX_FRAME_SIZE, stdin) != NULL )
        {
            char *length_start = strstr(buf,CONTENT_LENGTH);
            if (length_start == NULL) continue;
            char *length_end = strstr(length_start,"\r\n");
            int tmp = *length_end;
            *length_end = 0;
            length_start += strlen(CONTENT_LENGTH);
            *length_end = tmp;
            length = atoi(length_start);
            break;
        }

        while( fgets(buf, MAX_FRAME_SIZE, stdin) != NULL )
        {
            // parse body
            char *payload = strstr(buf,PAYLOAD);
            if (payload != buf) continue; // if not start with "\r\n"
            break;
        }
        if (fread(buf,length,1,stdin) == 0) break;
        fwrite(buf,length,1,stdout);

        char fname[16];
        snprintf(fname,15,"%05d.jpg",seq++);
        FILE *out = fopen(fname,"w");
        fwrite(buf,length,1,out);
        fclose(out);
    }
}
```
```bash
PARSER="$HOME/hgfs/Dropbox/symafpv/a.out"
HOST="http://192.168.1.1:80"
DATE=`date +%F_%T`
mkdir -p $DATE
cd $DATE
ID=`wget -q -O - "$HOST/request_av.cgi?User=admin&pwd=" | grep "var id=" | grep -o -P "[0-9]+"`
wget -q -O - "$HOST/Videostream.cgi?User=admin&pwd=&id=$ID" | $PARSER | vlc /dev/stdin
avconv -framerate 15 -i %05d.jpg -codec copy encoded15.mov
rm *.jpg

```