2015年12月22日火曜日

動画配信サーバを作ってみた

Google、Apple、Amazon、IT関連の大企業はみんな動画配信サービスを行っています。
世の中に動画配信サーバの作り方みたいのがあまりないし、セキュアに配信しないといけないし、どうやってやっているのか勉強するために動画配信サーバを作ってみました。

まず、iPhoneのコンテンツ配信の仕様のドキュメント、Safari Web ContentGuideを見ます。

https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/

pdf版もあるのでダウンロードするのが良いと思います。

読むのが大変なので要約すると、HTTPでVideoやAudioを配信するサーバはRangeヘッダーに対応しなくてはいけないと書かれています。


次に、サーバを立てます。
ApacheでもAnhttpdでもなんでもよいです。

次にサーバの設定でCGIを許可します。
許可の仕方はサーバごとに異なるので、サーバのドキュメントを見て設定してください。

ここからが重要です。
次に、nphスクリプトを作ります。
nphスクリプトというのはスクリプトのファイル名がnph-で始まるCGIスクリプトを指します。
nph-で始まると通常のCGIと何が違うのかというと、HTTPのレスポンスヘッダーをサーバ側で自動で送らないので、クライアント側で自由なレスポンスを返すことができます。

npHスクリプトをかくことで、Rangeヘッダーの時に必要な206 Partial Contentをサーバが返すことができるのです。


あとはスクリプトを作ってストリームを返せば動画配信サーバが完成

ということで最後にスクリプトと言っておきながらC言語でnphスクリプトを書いて見ました。

あとは環境変数のQUERY_STRINGでセッション値を取得するなどして、セキュアにストリームを配信すれば誰でも動画配信サービスをすることができます。

動画配信サーバってめちゃくちゃ簡単。
こんなにも簡単な技術であんなにお金を儲けられちゃうなんて。


npg-stream.c
----------------------------------------------
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#ifdef WIN32
#include<fcntl.h>
#include <io.h>
#endif

#ifdef _MSC_VER
#if _MSC_VER >= 1400
#pragma warning( disable : 4996 )
#pragma warning( disable : 4819 )
#endif
#endif


//
//  binarymode
//
static void setstdoutmode()
{
#ifdef WIN32
_setmode(_fileno(stdout), _O_BINARY);
#endif

}


//
// util
//

void mygetenv(char* buf,const char* moji, int sz)
{
char *p;
if (buf == NULL || moji==NULL || sz<1)return;
p = getenv(moji);
if (p == NULL)p = "";

buf[0] = 0;
strncpy(buf, p, sz);
buf[sz-1] = 0;

}
int mymin(int a, int b)
{
if (a < b)return a;
return b;
}

FILE* fzengo_open(const char* file, const char* mode, const char* path)
{
FILE* ret = NULL;
char buf[4096];
if (file == NULL || mode == NULL || path == NULL)return ret;

if (strlen(file) > 1000)return ret;
if (strlen(path) > 1000)return ret;

// 1
sprintf(buf, "%s/%s", path, file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 2
sprintf(buf, "../%s/%s", path,file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 3
sprintf(buf, "../../%s/%s", path, file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

// 4
ret = fopen(file, mode);
if (ret != NULL)return ret;

// 5
sprintf(buf, "../%s", file);
ret = fopen(buf, mode);
if (ret != NULL)return ret;

return ret;
}

FILE* fall_zengo_open(const char* file, const char* mode)
{
FILE* ret = NULL;

ret = fzengo_open(file, mode, "excel");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "music");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "video");
if (ret != NULL)return ret;
ret = fzengo_open(file, mode, "movie");
if (ret != NULL)return ret;

return ret;
}

const char* getmime(const char* name)
{
const char* ret = "application/octet-stream";

if (name == NULL)return ret;
if (strstr(name, ".mp3")) ret = "audio/mpeg";
if (strstr(name, ".m4v")) ret = "video/x-m4v";
return ret;
}

//
// print
//
static void print(char* p)
{
if (p == NULL)return;
printf("%s\r\n",p);
}

static void printresponse()
{
print("HTTP/1.1 200 OK");
print("Connection: close");
print("Pragma: no-cache");
print("Cache-Control: no-cache");
print("Accept-Ranges: bytes");
}


static void printresponse_range(int st, int ed, int fsz)
{
char buf[256];
//  causes proxy issue
print("HTTP/1.1 206 Partial Content");
print("Connection: close");
print("Pragma: no-cache");
print("Cache-Control: no-cache");
print("Accept-Ranges: bytes");
sprintf(buf, "Content-Range: bytes %d-%d/%d", st, ed, fsz);
print(buf);
}


static void printheader(const char* p)
{
char buf[256];
if (p == NULL)return;
sprintf(buf, "Content-Type: %s", p);
print(buf);
print("");
}

static void printheader_size(const char* p,int sz)
{
char buf[256];
if (p == NULL)return;
sprintf(buf, "Content-Type: %s", p);
print(buf);
sprintf(buf, "Content-Length: %d", sz);
print(buf);
print("");
}

static void printbody(char*p, int l)
{
if (p == NULL || l < 1)return;
fwrite(p, 1, l, stdout);
}


static void print_file(char *file)
{
FILE* fp;
char buf[1024 * 256];
int sz, fsz;
char* mime;

if (file == NULL)return;
fp = fall_zengo_open(file, "rb");
if (fp == NULL)return;

fseek(fp, 0, SEEK_END);
fsz = ftell(fp);
fseek(fp, 0, SEEK_SET);

printresponse(fsz);

mime = getmime(file);
printheader_size(mime, fsz);

do{
sz = fread(buf, 1, mymin(fsz, sizeof(buf)), fp);
if (sz < 1)break;
printbody(buf, sz);
fsz -= sz;
if (fsz < 1)break;
} while (1);
fclose(fp);

}

static void print_file_range(char *file,int st,int ed)
{
FILE* fp;
char buf[1024 * 256];
int sz, fsz,ssz;
char* mime;

if (file == NULL)return;
fp = fall_zengo_open(file, "rb");
if (fp == NULL)return;

fseek(fp, 0, SEEK_END);
fsz = ftell(fp);
fseek(fp, 0, SEEK_SET);

if (ed == -1)ed = fsz - 1;
ssz = ed - st + 1;

if (ed < 0 || st < 0)goto end;
if (st + ssz>fsz)goto end;


fseek(fp, st, SEEK_SET);
printresponse_range(st,ed,fsz);

mime = getmime(file);
printheader_size(mime, ssz);

do{
sz = fread(buf, 1, mymin(ssz, sizeof(buf)), fp);
if (sz < 1)break;
printbody(buf, sz);
ssz -= sz;
if (ssz < 1)break;
} while (1);

end:
fclose(fp);

}



//
// main
//
int main(int argc, char *argv[])
{
char env[1024];
char query[1024];
char buf[1024];
char* filename="hoge.m4v";
char* p;
int f = 0, st = -1, ed = -1;
int fno = 0;

setstdoutmode();

mygetenv(env,"HTTP_RANGE",sizeof(env));
mygetenv(query, "QUERY_STRING", sizeof(env));

p = strstr(query, "f=");
if (p)sscanf(p + 2, "%d", &fno);
if (fno < 1)fno = 1;


if (filename[0] == 0)strcpy(filename, "null");

p = strstr(env, "bytes=");
if (p){
p += 6;
sscanf(p, "%d-%d", &st, &ed);
}

if (st != -1)f = 1;

if (f){
print_file_range(filename, st, ed);
}
else{
print_file(filename);
}
return 0;
}

----------------------------------------------



Protocol Buffersを使ってC++でRPCをやってみた

Googleが作っているProtocolBuffersは、異なるPC間やプロセス間で関数コールやデータのやり取りをするライブラリです。いろいろな言語で動くので、サーバ間で分散処理を行うのにとても便利です。Googleのサーバ側の分散処理の最も重要な中核な技術といっても過言ではないと思います。ProtocolBuffersを制するものはサーバを制します。

しかしC++でこのProtocol Buffersを使ってRPCをやろうとするとちょっとめんどくさいからなのか、GoogleのProtocolBuffersの公式ドキュメントにもC++のRPCの完全なサンプルがありません。
みんなC++でProtocolBuffers使わないのかなぁ。ネットで検索しても世の中にあまりいいサンプルがありません。
なので、C++でProtocol Buffersを使ってTCP/IPでRPCをするサンプルを作ってみました。



まず、person.protoを定義します。
オプションにcc_generic_servicesをつけないとRPC用のServiceのヘッダーを生成してくれないので注意が必要です。


person.proto
-----------------------------

option cc_generic_services = true;

message Person {
  required int32 id = 1;
  required string name = 2;
}

message HelloResponse {
  required string message = 1;
}

service HelloService {
  rpc hello(Person) returns (HelloResponse);
}

-----------------------------
このファイルをprotocにかけて、person.pb.hとperson.pb.ccを生成します。




次にサーバ
main_server.cc
-----------------------------
#include "person.pb.h"
//#include "HelloRpcChannel.h"

#include "sock2_local.h"

static int myrecv(SOCKET sc,char* buf,int sz)
{
int ret = 0;
int readed = 0;
while (sz > 0){
ret = recv(sc, buf, sz, 0);
if (ret < 1)break;
sz -= ret;
buf += sz;
readed += sz;
}
return readed;
}

void hello(int id, const char* name, char* ret, int sz)
{
printf("id=%d\n", id);
printf("name=%s\n", name);
strcpy(ret, "hagehagehagehage");
}

int main(int argc, char* argv[]) {

SOCKET s=-1;
char buf[1024];
int sz = 0;
char*p;
int ret;
SOCKADDR_IN sa;
memset(&sa, 0, sizeof(sa));

winsock_init();

sa.sin_addr.s_addr = htonl(INADDR_ANY);
sa.sin_port = htons(12340);
sa.sin_family = PF_INET;

s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1)goto error;
ret = bind(s, (SOCKADDR*)&sa, sizeof(sa));
if (ret == -1){
printf("biund error\n");
goto error;
}
ret = listen(s, 20);
if (ret == -1){
printf("listen error\n");
goto error;
}
while (1){
char resbuf[256];
Person person;
HelloResponse res;
SOCKET sc;
SOCKADDR_IN sac;
memset(&sac, 0, sizeof(sac));
sz = sizeof(sac);
printf("before accept\n");
sc = accept(s,(SOCKADDR*)&sac,&sz);
printf("after accept\n");
if (sc == -1){
printf("accept error\n");
break;
}

ret = myrecv(sc, (char*)&sz, 4);
sz = ntohl(sz);
if (sz<1 || sz>1024)goto next;
printf("server recv data size=%d\n",sz);
myrecv(sc, buf, sz);
person.ParseFromArray(buf, sz);

resbuf[0] = 0;
hello(person.id(), person.name().c_str(), resbuf, sizeof(resbuf));
res.set_message(resbuf);
sz=res.ByteSize();
if (sz<1 || sz>1024)goto next;
printf("server send response %d\n",sz);
ret = htonl(sz);
send(sc, (char*)&ret, 4, 0);
res.SerializeToArray(buf, sz);
send(sc, buf, sz, 0);
next:
closesocket(sc);
}

error:
if(s!=-1)closesocket(s);
winsock_done();




return 0;
}
-----------------------------




最後にクライアント
main_client.cc
-----------------------------
#include "person.pb.h"

#include "sock2_local.h"


static int myrecv(SOCKET sc, char* buf, int sz)
{
int ret = 0;
int readed = 0;
while (sz > 0){
ret = recv(sc, buf, sz, 0);
if (ret < 1)break;
sz -= ret;
buf += sz;
readed += sz;
}
return readed;
}


class HelloRpcChannel :public google::protobuf::RpcChannel{
public:
HelloRpcChannel()
{
}
virtual ~HelloRpcChannel()
{
}
virtual void CallMethod(const google::protobuf::MethodDescriptor* method,
google::protobuf::RpcController* controller,
const google::protobuf::Message* request,
google::protobuf::Message* response,
google::protobuf::Closure* done)
{
SOCKET s;
SOCKADDR_IN sa;
char buf[1024];
int sz;
int ret;

winsock_init();
s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1)goto error;

memset(&sa, 0, sizeof(sa));
sa.sin_port = htons(12340);
sa.sin_addr.s_addr = htonl(0x7f000001);
sa.sin_family = PF_INET;

ret = connect(s, (SOCKADDR*)&sa, sizeof(sa));
if (ret == -1){
printf("client connect NG\n");
goto error;
}
else{
printf("client connect OK\n");
}
sz = request->ByteSize();
if (sz<1 || sz>1000)goto error;

request->SerializeToArray(buf, sz);

printf("client request data size=%d\n", sz);
ret = htonl(sz);
ret = send(s, (char*)&ret, 4, 0);
ret = send(s, (char*)buf, sz, 0);
ret = myrecv(s, (char*)&sz, 4);
sz = ntohl(sz);
if (sz<1 || sz>1000)goto error;

ret = myrecv(s, buf, sz);
printf("client response data size=%d\n", sz);
response->ParseFromArray(buf, sz);
//printf("response Message=%s\n",esponse.message().c_str());
error:
if (s != -1)closesocket(s);
done->Run();
winsock_done();
}

};


class MyRpcController : public google::protobuf::RpcController{
public:
MyRpcController()
{
}
virtual ~MyRpcController()
{
}
virtual void Reset()
{
}
virtual bool Failed () const
{
return false;
}
virtual std::string ErrorText() const
{
return "";
}
virtual void StartCancel()
{
}
virtual void SetFailed(const std::string& reason)
{
}
virtual bool IsCanceled() const
{
return false;
}
virtual void NotifyOnCancel(google::protobuf::Closure* callback){
}
};

class MyClosure : public google::protobuf::Closure{
public:
MyClosure()
{
}
virtual ~MyClosure()
{
}
virtual void Run()
{
}
};


void hello(int id, const char* name, char* buf, int sz)
{
HelloRpcChannel hrc;

HelloService_Stub hss(&hrc);
Person req;
HelloResponse res;
MyRpcController con;
MyClosure clo;


if (name == NULL || buf == NULL || sz < 1)return;
buf[0] = 0;

req.set_id(123);
req.set_name("hogehoge");
hss.hello(&con, &req, &res, &clo);

strncpy(buf,res.message().c_str(),sz);
buf[sz - 1] = 0;
}


int main(int argc, char* argv[]) {
char buf[256];
hello(123, "hoge", buf, sizeof(buf));
printf("hello() return=>>%s<<\n ",buf);
return 0;
}
-----------------------------

DeepLearningのライブラリTensorFlowとかもこうやって分散処理させているのね。
多言語間のプロセス間通信って意外と簡単。
これで誰でも分散処理ができる。


2015年12月8日火曜日

Android NDKのatof()問題

AndroidにはCランタイムライブラリのatof()関数がある機種とない機種があります。
正確にいうとatof()関数がインライン展開される機種とされない機種があります。

これらの関数を使うプログラムでは、ランタイムライブラリとNDKのバージョンが合わず動かないことがあります。

D/dalvikvm(18948): Trying to load lib /data/app-lib/com.yomei.ndkshell/libndkshell_.so 0x41ed4ba8
E/dalvikvm(18948): dlopen("/data/app-lib/com.yomei.ndkshell/libndkshell_.so") failed: Cannot load library: soinfo_relocate(linker.cpp:975): cannot locate symbol "atof" referenced by "libndkshell_.so"...
W/System.err(18948): java.lang.UnsatisfiedLinkError: Cannot load library: soinfo_relocate(linker.cpp:975): cannot locate symbol "atof" referenced by "libndkshell_.so"...
W/System.err(18948):    at java.lang.Runtime.loadLibrary(Runtime.java:371)
W/System.err(18948):    at java.lang.System.loadLibrary(System.java:535)
W/System.err(18948):    at com.yomei.SomeJni.<clinit>(SomeJni.java:8)
W/System.err(18948):    at com.yomei.SomeMain.main(SomeMain.java:283)
W/System.err(18948):    at com.yomei.SomeMain$MainActivity$1.run(SomeMain.java:357)

こんな風に怒られてしまいます。

この問題を解決するために、ランタイムライブラリ側ではatofを必ず持っていないといけなく、NDK側ではインライン展開されるヘッダーファイルでないといけないのですが、一部の古いランタイムライブラリと最近のNDKの組み合わせではそうなっていない・・・。
Google自身も64ビット化の対応で大変でもう32ビットのルールのことを忘れてるでしょ?


ネットで解決方法を調べてみたのですが、どれもまだ正しくないというかなんというか。
すべての機種で動かいないのです。

ということで、問題を分析して解決方法を考えてみました。
わかってしまえば、解決方法はすごい簡単なのですが、自分のsoファイルにatof()関数を追加すればよいだけです。こんな感じ。

atof_.c
------------------------------
#include <stdio.h>
#include <ctype.h>
#include "atof_.h"

#ifdef ANDROID
double atof(char *s)
#else
double atof__(char *s)
#endif
{
double a = 0.0;
int e = 0;
int c;
while ((c = *s++) != '\0' && isdigit(c)) {
a = a*10.0 + (c - '0');
}
if (c == '.') {
while ((c = *s++) != '\0' && isdigit(c)) {
a = a*10.0 + (c - '0');
e = e-1;
}
}
if (c == 'e' || c == 'E') {
int sign = 1;
int i = 0;
c = *s++;
if (c == '+')
c = *s++;
else if (c == '-') {
c = *s++;
sign = -1;
}
while (isdigit(c)) {
i = i*10 + (c - '0');
c = *s++;
}
e += i*sign;
}
while (e > 0) {
a *= 10.0;
e--;
}
while (e < 0) {
a *= 0.1;
e++;
}
return a;
}

------------------------------


atof_.h
------------------------------
#ifndef ATOF__H_
#define ATOF__H_


#ifdef __cplusplus
extern "C" {
#endif


double atof(char *s);
double atof__(char *s);


#ifdef __cplusplus
}
#endif


#endif
------------------------------

これで自作アプリでatof()関数を使用しても最新のNDKと古い32ビットの機種の組み合わせでも動きます。


2015年11月17日火曜日

TensorFlowを勉強中

先日Googleが公開した機械学習ライブラリであるテンソルフローについて毎日こっそり勉強中です。
まだざっとしか見れていないのですが、機械学習を行う時に問題となる、どのように一台のPCから複数のPCで分散処理をさせるかとか、どのように学習結果を視覚化するかとか、PCで学習させたデータをどのようにスマートフォンで使うかなど、他の機械学習のライブラリで問題となる点を全部解決しているように思えます。

すごすぎる。

2015年11月12日木曜日

JavaScriptで他のファイルをincludeする2


前回、JavaScriptで他のファイルをincludeする方法について簡単に書きました。
しかし、前回の方法では複数のファイルにinclude関数が宣言されていた、以下のように同じファイル名がある場合に多重にファイルをincludeしてしまいあまり実用的ではありません。

include("test.js");
include("test.js");

そこで、今回は前回の関数をすこし修正し、これらの問題を解決した完成版のinclude関数を作ります。



main.js
------------------------------------
if(typeof(include)=="undefined"){include_list={};include=function(src){
    var xhr=null;if(include_list[src])return;include_list[src]=1;
    if (window.XMLHttpRequest)xhr=new XMLHttpRequest();
    else if(window.ActiveXObject)try {
        xhr=new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){xhr=new ActiveXObject("Microsoft.XMLHTTP");}
    xhr.open("GET",src,false);xhr.send("");(0,eval)(xhr.responseText);
}}
//↑include関数を使うすべてのjsファイルの先頭に入れる

include("test.js");
println("test.js include OK");
------------------------------------


test.js
------------------------------------
if(typeof(include)=="undefined"){include_list={};include=function(src){
    var xhr=null;if(include_list[src])return;include_list[src]=1;
    if (window.XMLHttpRequest)xhr=new XMLHttpRequest();
    else if(window.ActiveXObject)try {
        xhr=new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){xhr=new ActiveXObject("Microsoft.XMLHTTP");}
    xhr.open("GET",src,false);xhr.send("");(0,eval)(xhr.responseText);
}}
//↑include関数を使うすべてのjsファイルの先頭に入れる

include("jquery-1.11.3.js");

println=function(src)
{
 alert(src);
}


jQuery().ready(function()
{
 println("jQuery OK");
}

------------------------------------


こんな感じです。
これでどんな順番にファイルをincludeしても正しく動きます。
ちなみにこの方法はjQueryも動的にincludeできます。
これであこがのJavaScriptファイルを分割してみんなで作業できる。



2015年11月11日水曜日

JavaScriptで他のファイルをincludeする

昨日に続きいろいろな言語のめんどくさい部分をどうにかするシリーズ。

JavaScripの中から別ファイルのJavaScriptを読み込みたいときがよくあります。
複数人で作業するときや、スマホ対応するときに読み込むJavaScriptファイルを動的に変えたり。
いつもHTMLの中に読み込むJavaScriptファイルを固定的に書くのはとてもめんどくさい。
JavaScripファイルを分割して読み込みたい要求はたくさんあります。

しかしWebで調べてもみたのですが、JavaScriptを動的にincludeする方法はChromeで動かなかったり、どれもいまいち。

ということで、JavaScriptファイルを動的に読み込むinclude関数を作ってみました。


読み込む側(main.js)
--------------------------------------

include=function(src){
    var xhr=null;
    if (window.XMLHttpRequest)xhr=new XMLHttpRequest();
    else if(window.ActiveXObject)try {
        xhr=new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){xhr=new ActiveXObject("Microsoft.XMLHTTP");}
    xhr.open("GET",src,false);xhr.send("");eval(xhr.responseText);
}

include("test.js");
println("Hello!");

--------------------------------------





読み込まれる側(test.js)
--------------------------------------
println=function(src){
alert(src);
}
--------------------------------------

上記場合の注意点として、関数や変数はvarをつけないで「hoghoge=」として宣言しないといけないです。include()関数は内部でeval()関数を使用しているのですが、Chromeとかではeval()関数内で宣言したvar変数や関数がローカル変数として扱われてしまうからです。varをつけないで宣言するとグローバル変数として扱われ、元のJavaScriptファイルから呼び出せます。

でもこれだとincludeされる関数を作るのがめんどくさい・・・。
しかも外部のライブラリ使えないジャン。

じゃあどうするのか。
ここからが本日最大のポイント。
「eval」のところを「(0,eval)」にすればよさそうな気がする。

自分で書いていて思うんですが、
はぁ僕何言っての?
いっている意味まったくもってわからない。

 (function(){ return eval;}(0))('hoge');


上記の式が等価なのですが、詳しくは以下のサイトに書いてあります。
http://qiita.com/SFPGMR/items/7a09507eead5b5db8d17

簡単にまとめると、JavaScriptのeval関数の仕様では、関数を直接呼び出すと関数内で宣言された変数のスコープが呼び出し元の関数となるのですが、関数を変数経由で間接的に呼び出すと変数のスコープがグローバルになるのです。
そして、eval関数を簡単に間接的に呼び出す方法が、(0,eval)('hoge')なのです。

ということでeval部分を修正してみました。


読み込む側(main.js)
--------------------------------------
function include(src){
    var xhr=null;
    if (window.XMLHttpRequest)xhr=new XMLHttpRequest();
    else if(window.ActiveXObject)try {
        xhr=new ActiveXObject("Msxml2.XMLHTTP");
    }catch(e){xhr=new ActiveXObject("Microsoft.XMLHTTP");}
    xhr.open("GET",src,false);xhr.send("");(0,eval)(xhr.responseText);
}

include("test.js");
println("Hello2!");
--------------------------------------



読み込まれる側(test.js)
--------------------------------------
function println(src){
alert(src);
}
--------------------------------------


やったーinclude関数ができた。
ちゃんとincludeが終わるまで関数をぬけないので、includeが終わった直後からincludeした関数使えるし、includeしたファイルの中からさらにincudeできるし、完璧。

セキュリティーの関係上、他のサーバのファイルはincludeできないのはAJAXと同じです。

eval関数のこの仕様はEcmaScript5から適用されて、それ以前の仕様では変数はローカルスコープになるようです。
したがって、読み込まれる方の関数の形式は後者の方法はInternetExploler8では動かないらしいので、InternetExploler8に対応したい場合は前者の方法で、InternetExploler9以降やスマホのみでよい場合はこの方法が使えます。
読み込む方のinclude関数は後者の方法にしておけばどれでも大丈夫です。
InternetExploler8のシェア2%くらいしかないので、ほとんど無視できますが。

JavaScriptほんとうに奥が深いです。

2015年11月10日火曜日

JavaのRuntime.exec(String cmd)をスペース対応する

ITFFFとかいうサービスを使ってみようと思い、ブログに投稿するとFacebookにも自動で投稿するように設定してみました。
なので久しぶりに技術ブログを更新。


JavaのRuntime.exec(String cmd)メソッドはとっても便利なのですが、cmd文字列の中のファイル名やコマンドオプション文字列にスペースが入っている場合に動作がおかしくなります。



cmd="ls *.bat";

たとえば、上記のパラメーターでRuntime.exec()を呼び出すときちんと動きます。

cmd="sh -c \"ls *.bat\"";

しかし、上記のパラメーターのようにコマンド文字列に'"'でくくった部分がある場合、Runtime.exec()を呼び出すとエラーになてしまい、きちんと動きません。
C言語とか他の言語では動くのに。

ネットの一部の掲示板ではshコマンドのパスが悪いとかいろいろ書かれていますが、どれも原因がいまいち。しかもWindowsではきちんと動くのですが、MacやLinuxでは動かないのです。
そこで、動かない原因を解析しました。


JAVAの仕様書を見ると、Runtime.exec(String cmd)はStringTokenizerを使ってRuntime.exec(String[] cmd)に分解されると書かれています。
StringTokenizerは上記の文字列を以下のように分解するようです。

String[] cmd={"sh","-c","\"ls","*.bat\""};

StringTokenizerはスペースがくるとそこで文字列を強制分割してしまうので、'"'でくくった部分を正しく解釈してくれないようです。

似たようなメソッドで文字列の配列を渡すRuntime.exec(String[] cmd)メソッドならばスペースがあっても大丈夫なのですが、Runtime.exec(String cmd)メソッドはスペース対応してないので、仕様書を見る限り、正しく動作しないようです。

Runtime.exec()メソッドにStringの配列で渡せばよいのですが、関数の戻り値とかをRuntime.exec()メソッドに渡す場合、配列だといろいろめんどくさいしなぁ。
他の言語ではだいたいどれもできるのに、Javaだけできないなんて。うーん。困った。


そこで今日一日よーく考えて、StringTokenizerの代わりとなる、execTokenizerメソッドを作ってみました。

これがあれば、こんな感じでスペースやリダイレクトがある文字列でも正しく動きます。

Runtime.exec(execTokenizer("sh -c \"ls *.bat\""))

急いで書いたのでexecTokenizer()がまだ汚い。
原理は確認できたのであとでソースをきれいにしよう。



----------------
public static String[] execTokenizer(String cmd)
{
 int j,i,l,c,lc=' ',ct=0;
 String[] ret=null;
 int hazime=0,owari=0;

 if(cmd==null)return null;

 try{

 //count umber
 l=cmd.length();
 for(i=0;i<l;lc=c,i++){
  c=cmd.charAt(i);
  if(c=='\"'){
   for(j=i+1;j<l;lc=c,i++,j++){
    c=cmd.charAt(j);
    if(c=='\"'){
     ct++;
     break;
    }
   }
  }
  if(c==' ' && lc!=' '){
   ct++;
  }
 }
 if(lc!=' ' && lc!='\"'){
  ct++;
 }
 if(ct==0){
  return ret;
 }
 ret=new String[ct];
 ct=0;
 //
 l=cmd.length();
 for(i=0;i<l;lc=c,i++){
  c=cmd.charAt(i);

  if(c!=' ' && lc==' '){
   hazime=i;
  }

  if(c=='\"'){
   hazime=i+1;
   for(j=i+1;j<l;lc=c,i++,j++){
    c=cmd.charAt(j);
    if(c=='\"'){
     owari=j;
     ret[ct]=cmd.substring(hazime,owari);
     ct++;
     break;
    }
   }
  }
  if(c==' ' && lc!=' '){
   owari=i;
   ret[ct]=cmd.substring(hazime,owari);
   ct++;
  }
 }
 if(lc!=' ' && lc!='\"'){
  owari=i;
  ret[ct]=cmd.substring(hazime,owari);
  ct++;
 }

 }catch(Exception e){};

 return ret;
}




2015年3月25日水曜日

Webでプログラムを書いてコンパイルできるようにしてみた

最近Webのブラウザ上でプログラムを開発できるサービスとかが増えています。
これができるようになった理由は、数年前にACEというブラウザのJavaScriptで動く、テキストエディターが登場したからです。
みんなこのブラウザ上のエディターを使っていろいろな開発サービスを作っているようです。
ということで、僕もACEを使ってプログラムをコンパイルするサーバを立ち上げてみました。

クライアント側はHTMLとJavaScriptで簡単に作れます。
サーバ側をどの言語で実装するか迷います。とりあえずJavaのJSPで作ってみました。
サーバ側もファイルを作ってシェルスクリプトを動かすだけなのでとても簡単。

この開発環境を作る作業もとても楽しいですね。

これでいままで開発したコードを一生懸命Cygwin、VisualStudio、Linux、MacOSXでコンパイルしていたのを全部自動化できます。







2015年3月10日火曜日

mbedでLEDチカチカしてみた

この前のブログで、32ビットARMマイコンのLPC1114FN28を使った開発基板を設計しました。
P板.comに基板を注文したら数日で基板が納品されてきました。

さっそく、自分で設計した基板でLEDチカチカするARMプログラムを書き込みしてみました。


mbedの開発環境はこんな感じでWeb上の開発環境でソースコード管理とコンパイルができるのでとても面白いですね。

-----------------
#include "mbed.h"

DigitalOut myled(dp2);

int main() {
    while(1) {
        myled = 1;
        wait(0.2);
        myled = 0;
        wait(0.2);
    }
}

-----------------

このコードを基板に転送します。




無事にLEDがチカチカしました。
めでたしめでたし

2015年2月24日火曜日

ハイサイドスイッチを作ってみた

最近僕のまわりではIOTが流行っているようで、ラズベリーパイなどからいろいろなデバイスの電源をON/OFFする回路を作りたいという質問をよく受けます。
FETかトランジスタを使ってハイサイドスイッチを作れば良いのですが、ネットで調べるとどれも微妙に回路図が違うので、どれが良いのか迷うようです。

Webで実行できる回路図シュミレータCircuitLab(https://www.circuitlab.com)でどの回路が良いのか実験してみました。

また、半導体で電源をコントロールする場合、コントロールするデバイスのプラス側でスイッチするハイサイドスイッチと、デバイスのマイナス側でスイッチするローサイドスイッチというのがあります。ローサイドスイッチの方が部品数が少なく良いのですが、人間の感覚として直感的に解りづらいし、グランドが共通でなくなるのがとてもいやです。
僕もP型半導体とN型半導体で同じ回路を組めるようになるのに何年もかかりましたし、マイナスの電圧がかかったり電流が流れるって本当に解りづらいです。
そこで今回は直感的に解りやすいハイサイドスイッチで実験をしていきたいと思います。


1. トランジスタ編
まず古典的な手法。2SAと2SCのトランジスタを組み合わせる方法です。
きちんとON/OFFできています。問題点としてトランジスタ駆動は電流をたくさん使います。


2. FET編

ドランジスタとPチャネルFETを使うパターンです。
マイコンの電源がOFFの時は電流が流れないようにトランジスタを挟んでいます。
トランジスタを使う回路とくらべ少ない駆動電流で大きな出力を取り出せるのが特長です。もっとも一般的なやり方?
きちんと動いています。
でも低電圧で駆動する場合、個人的にはベースとグランド間の抵抗がいらない気がします。

これでシュミレーションしてみました。
やっぱり、きちんと動きます。

Webの回路図シュミレータ面白いですね。実際に回路を組まなくても
全部パソコンだけで実験できてしまう。すげー。



2015年2月23日月曜日

基板を作ってみた

ここ数年、回路図と基板を作っていたなっかので、暇つぶしに作って見ました。
bschv3の回路図エディターでネットリストを出力して、CadlusXでアートワークしてみました。
LPC1114FN28用のマイコン開発基板。
LPC1114FN28ですが、秋月電子で140円で売っている、ARM32ビットマイコン。
すごい安いです。これでいろんなことできそう。



2015年2月10日火曜日

プラたくに行ってきた

もう一か月以上たってしまいましたが、2015年のよーめー日記は一般のファミリー層の人も楽しめる内容を書いて、広告費を稼ぐと心に決めたのでした。

ということで、ファミリー層でも楽しめる日記第一回。葛飾区立石にある、男の子に大人気のプラレールが楽しめる喫茶店、「プラたく」に行ってきました。
この喫茶店にはたたみ一畳程のスペースにプラレールが展示してありまして、自分のプラレール車両を走らせて遊ぶことができます。たった一畳だけとなめてかかってはいけません。こんな感じになっているんです。
すぐよこのテーブル席からみるとこのすごさに圧巻されます。
食事の方も喫茶店としてはかなり変わっていて、隣のお寿司屋さんで作ってるお寿司などが頼めたりもします。
一番のおすすめはプラレールの形のケーキ。かなりよくできています。
はやぶさ、こまち、ドクターイエローの三種類があります。
テーブルが五つほどしかなく、休日の昼間はとても混んでいるので、平日に行くことをお勧めします。



PIC32MX用の開発ボードを作ってみた

PIC32MXマイコンを使って、いろいろ組み込みのプログラムをしてみようと思い、LEDをチカチカさせられる、通称「Lチカ」開発ボードを作ってみました。


回路図はこんな感じです。すべてのPIC24とPIC32MXのデータシートに目を通して、多くの種類のPICにプログラムを書き込んでLEDを光らせるように設計したつもりです。
あとは、あしながのピンソケットを買って、こんな風に基板にとりつれば、Arduinoのように基板を何枚も重ねられるので、スイッチとかセンサーとかSDカードリーダーを上下の基板に取り付けられます。

プログラム書き込み用のピンヘッダーは縦タイプのものでなくてL字タイプのものをつけるべきでしたね。そうしないと上に基板をつけたと時にプログラムが書き込めない・・・。


2015年2月9日月曜日

SwiftでもC++でもコンパイルできるようにHello Worldを書いてみた。

プログラム言語Swiftを勉強していたら#ifが使えるらしいことが書いてありました。
これは、SwiftでもC++でもコンパイルできるソースコードが書けるのではないかと思い、早速両方のコンパイラでコンパイルできるHello Worldを書いてみました。

Swiftは#ifの中も全て構文解析するので#ifでくくるだけでは両方でコンパイルすることはできません。
従ってSwiftはコメントがネスとできるという機能も使ってC++特有の部分を記述しています。
両方の文法を満たすのって結構難しいです。

想像していた以上に読みづらく、もう全く実用にならないです。


-------------------


#if SWIFT
    /*/**/
#endif
    
#include <stdio.h>

#if SWIFT
    /**/*/
#endif



#if SWIFT
    /*/**/
#endif

static void println(const char* a){
    printf("%s\n",a);
}

#if SWIFT
    /**/*/
#endif

#if SWIFT
    /*/**/
#endif
    
int main(){

#if SWIFT
    /**/*/
#endif



println("Hello, World!");



#if SWIFT
    /*/**/
#endif

}
   
#if SWIFT
    /**/*/
#endif



2015年1月26日月曜日

Raspberry Pi とか Arduino 高くない?

最近、ちょっとしたブームになっている、Raspberry Pi とか Arduino。
日経新聞によると安くて小さくて遊べるから人気らしいです。

電子工作世代にブーム 超小型PC「ラズベリーパイ」
http://www.nikkei.com/article/DGXMZO79030620Z21C14A0000000/

Raspberry Pi が5000円前後、Arduinoは2000円前後で買えるため、ベンチャー企業がガジェット開発するときにもよく使います。

これらを一個だけ買うのならば安いんですが、同じものをいくつか用意しようとすると数万円規模でお金がかかるので、気軽にたくさん買えないんですよね。というかひとつ数千円って高くないですか?同じお金で発泡酒が約50本、一日二本飲むとすると一ヶ月飲める分買えるんです!

小、中学生や個人で実験とか試作とかするならばできれば500円以下でないと、たくさん買えない気がします。
そこで500円以下で32ビットの組み込みのガジェットが比較的簡単に製作できる最小の構成を考えてみました。


1 CPU

PIC32MX120F032 1個200円
二十年前だと数十万円した32ビットのMIPSが、いまはたった200円
価格は倍位しますが、TV出力できるタイプのものもあります。

2 基板
秋月電子で売っている、ブレッドボードのパターンが印刷されている基板です。
ブレッドボードで考えた回路図をそのまま基板にできます。
一枚 80円

3 電源系


3.3V 150mAの低損出レギュレータが1個50円
ACアダプターを基板につける変換基板が全部で100円
レギュレータの入出力端子の前後とPICのVCAP端子につける10uFのセラミックコンデンサーが3個で60円
MCLR端子につける10kオームのプルアップ抵抗が10円

4 コネクタ

基板につけるピンヘッダー 1個40円



これで全部品終わり。ピンヘッダーがいらなければ1セット500円、ピンヘッダーを含めても1セット540円くらいで32ビットの組み込みプログラムができます。安くない?
これならば作ったものを気軽に人にあげたり、試作ができる気がします。
同じものを数百個作るんであれば量産するより安い気もします。


あと、実際にプログラムをマイコンに書き込むのに4500円のPicKit3が必要ですが、これは一回買えばよいので。

また、作った基板でパソコンやスマートフォントと通信したい場合、RN42無線モジュール評価キット2400円をつなげれば、USBシリアルやBluetoothで通信できます。


2015年1月22日木曜日

ZigBeeを買ってみた。

ZigBeeの規格を調べていたら、規格上1500メートル離れたところでも無線シリアル通信ができるようです。さらに、あいだに中継用のZigBeeを挟めばさらに遠くまで無線シリアル通信できるようです。

 ZigBeeで遊んでみたくなったのでさっそく実験基板を秋月電子で買ってみました。

まずこれ、Xbee USBインタフェースボートキット。これでPCからZigBeeをセットアップしたり、相手のZigBeeとシリアル通信したりします。電源の役割も果たします。


次はこれ、Xbee Pro ZB S2B。ZigBeeのモジュール本体基板です。この基板いろいろな種類のものがたくさんあり、どれを買って良いのか迷います。とりあえずこれで良さそう。

 この二つを五千円くらいで揃えれば、一応Zigbeeで遊べるのですが、相手と通信するためには同じものを最低2セット、中継するにはさらにもう1セットいります。


純正のZigBee RF開発セットもあるのですが、これは三万円以上するので今回はパス。
でもこれ欲しい。





センサー用のケーブルを作ってみた。

スマホで温度とかはかってみようと思い、センサー用のケーブルを考えてみました。
左から、センサー、同軸ケーブル、Fコネクター、Fメス・RCAオス変換アダプター、RCA・DIP化キット

全部秋月電子や千石電子で買える部品ばかりで、こんな感じで部品をつなげて、センサーケーブルを作ります。

Arduinoとかにこのケーブルと、この前ブログで紹介した、RN42シリアルBluetooth変換基板をつなければ、スマホから簡単に温度とか明るさとかはかれるはず。