ドローン 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とかで開くと、リアルタイム中継が見られる。
<!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>
複数接続はできるが、つなげばつなぐほどきっと重くなる。
Digest認証を求められたときはユーザー名 admin
でパスワードは空白にする。
表示をリレーする
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()
ホントは中継ついでに保存もしたい。そのためには環状バッファを実装する必要がある気がする。
追記
Pythonを使わず、wgetとオリジナルのC言語製 MJPEG スプリッターを使うと、VLCでリアルタイムで見ながら保存もできるようになります。以下参照。
cc -O3 separate.c
でコンパイルして、 bash syma.sh
を実行。
デバッグ不足で一度目は映らなかったりするので閉じて再度トライのこと。
https://gist.github.com/keiya/cb9e2901ccc1e813c634
# 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);
}
}
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