2013年12月23日月曜日

opencvで処理した画像(動画)をandroid端末に送信して表示するあたりのメモ

以前、SimpleMjpegViewというライブラリを利用してwebcamの映像をandroid上に表示したが、今回は別の手法で(もっと原始的な形で)端末上に動画を表示する。

前回:

 webcam → (mjpegストリーム) → android端末

今回:
 webcam → PC(c++(Opencv)) → (jpeg) → android端末

 今回の方でやれば重たい画像処理を母艦PCやraspiで行ってその処理結果をandroidに送信できる。

とりあえず結果↓

 いいwebカメラ(ロジクールの1万のやつ)なのに若干カクつく。サイズを小さくしたり、jpg品質落とすことでましにできると思われる



opencvを使ったりしているのでMITライセンスになります。
強制終了前提のコードなのであくまでご参考、自己責任でお願いします。

↓MITLicenceに基づく告知

Some codes in OpenCV are utilized.
Therefore, before downloading, you have to agree to the following license.
//////////////////////////////////////////////////////////////////////////////////
IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
By downloading, copying, installing or using the software you agree to this license.
If you do not agree to this license, do not download, install,
copy or use the software.

                    Intel License Agreement

            For Open Source Computer Vision Library
Copyright (C) 2000, Intel Corporation, all rights reserved.
Third party copyrights are property of their respective owners.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
  • Redistribution's of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  • Redistribution's in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
  • The name of Intel Corporation may not be used to endorse or promote products
    derived from this software without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are disclaimed.
In no event shall the Intel Corporation or contributors be liable for any direct,
indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused
and on any theory of liability, whether in contract, strict liability,
or tort (including negligence or otherwise) arising in any way out of
the use of this software, even if advised of the possibility of such damage.

 ライセンス告知ここまで


まずC++側、環境はwin7 64bit Gcc(MINGW) opencvが必要(今回は2.4.7使用)


概要 : 
0、ソケット構築、android側からのアクセスを待ち受け

1、opencvで動画や画像フレームを取ってきて好きなように処理して最終的にjpgに圧縮 jpgのデータはvector<uchar>で保存される

2、jpgのデータサイズ(何バイトか)をバイナリ(intだと4byte)にする、地味なビット演算処理が必要(もっとスマートにできそうだが)。要は送信先でintとしてデータサイズを取れるようにする。これがわからないとあちらで正確なバッファが作れない

3、 jpgデータサイズとjpgデータをandroidに送る。
   vector<uchar>をソケットで送信できるconst char*に変換する部分の調査に時間がかかった(初見だとここで詰まる可能性あり)

4、おわり。上記処理をループさせている


(参考ソース)

#include <iostream>
#include <windows.h>
#include <winsock2.h>

#include "opencv2\highgui\highgui.hpp"
#include <vector>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sstream>

using namespace cv;
using std::stringstream;
int main()
{
~~~~~~~~~~~~~



ここにソケットの初期化(bindまで)の処理が入るが諸事情(参考文献の丸写しのため)によりカット
参考文献 : http://blog.pusheax.com/2013/07/windows-api-winsock-create-your-first.html

windowsソケットプログラミング(C++)の基本説明



~~~~~~~~~~~~~



    //////////////// start(original from here)

    int lis=listen(u_sock,3);
    /////////////// waiting client data   
   
    //opencvによる動画の取得
    VideoCapture cap(0);
   
    //メインループ
    while(1){
        stringstream ss;
        //クライアントからの受信用バッファ、何らかのコマンド受け取りを想定
        int receive_buf_size = 20;
        char vect[receive_buf_size];

        std::cout <<"start waiting client command" <<std::endl;
        //client adress define
        struct sockaddr_in client;
        int len = sizeof(client);

        //client connect waiting・・・クライアント定義、待ちうけはループ外でもいいかもしれない
        SOCKET sockCli = accept(u_sock, (struct sockaddr *)&client, &len);   
        //クライアントから20byte何かしら受け取る、必要に応じてパース
        int get=recv(sockCli,vect,receive_buf_size,0);
        if(get==SOCKET_ERROR){
            std::cout<<"Error in Receiving: "<<WSAGetLastError()<<std::endl;
            continue;
        }

        // receivedta(command) process 受け取ったデータの処理、Stringstreamを使って文字列にして表示
        Sleep(10);//data receive waiting クライアントからのデータを待つ
        std::cout <<"end receive"<<std::endl;
        ss.clear();
        String res_string;
        ss << vect;
        ss >> res_string;

        if (get <0){
            std::cout <<"no receive data"<<std::endl;
            //    break;
        }else{
            std::cout <<"receive data : ";
            std::cout <<res_string<<std::endl;
        }

        ss.clear();

        /////////// image data send process 画像の取得と送信   
        Mat frame;

        cap >> frame;

        int framesize = 1000000; ///Max size

        //送信データ用のバッファ
        vector<uchar> bufv;
        //ちゃんと変換されたかの確認用
        bool imok;
        //jpg圧縮用のパラメータ設定
        vector<int> jpgparam;
        jpgparam.push_back(CV_IMWRITE_JPEG_QUALITY);
        jpgparam.push_back(20); /* jpeg quality */
        //ここまでパラメータ設定

        //圧縮した画像をバッファに入れる
        imok=imencode(".jpg",frame,bufv,jpgparam);
       
        //Mat sframe;

        std::cout<<bufv.size()<<std::endl;

        //Send some message to remote host

        ///////////// jpg data sending
        std::cout <<"start sending"<<std::endl;

        std::cout <<"send size"<<bufv.size() <<std::endl;
        std::cout <<"senddata "<<bufv.data()<<std::endl;
        std::cout <<"start sending"<<std::endl;

        // sizeof jpg send to java  画像サイズ(何バイトか)をクライアントに送信する
        // intの数値をバイナリ(4バイト)に変換する。泥臭い処理
        vector<uchar> jpgsize_send(4);
        int jpgsize=bufv.size();
       
        //int(4byte)のバイナリを1バイトずつ送信用の配列に入れる。エンディアン注意   
        for (int i=0;i<4;i++){
        jpgsize_send[3-i]=(jpgsize >>(i * 8) );
        }
        ////////// bynary genelator

        std::cout <<"data size : "<< jpgsize <<"type size"<<sizeof(jpgsize) <<std::endl;
        //std::string siz=std::to_string(jpgsize);
        std::cout <<"type size"<<sizeof(jpgsize_send) <<std::endl;
       
        /////////////////////// send data to android
        //vectorをcharに変換してクライアントに送信、
        //ロベール本によるとreinteroret_castの使用には注意が必要らしい

        //ヘッダ部(今回は画像データバイト数のみ)送信
        int smsg_header=send(sockCli,reinterpret_cast<const char*>(jpgsize_send
                    .data()),jpgsize_send.size(),0);
        //画像データ送信
        int smsg=send(sockCli,reinterpret_cast<const char*>(bufv.data()),bufv.size(),0);

        ///////////////////////


        if(smsg==SOCKET_ERROR){
            std::cout<<"Error: "<<WSAGetLastError()<<std::endl;
            WSACleanup();
        }

        /*
        //get=recv(u_sock,vect,512,0);
        if(get==SOCKET_ERROR){
        std::cout<<"Error in Receiving: "<<WSAGetLastError()<<std::endl;
        }
        //std::cout<<vect<<std::endl;
        */
    }
    closesocket(u_sock);

~~~~~ ここから終了処理を適当に

}

------------------------------------------  C++ここまで



Java(Android側)、eclipse4.3(Kepler Service Release 1) androidosは4.4が入ったnexsus7(2012)


概要:androidのプロジェクトで新しいクラスを作って以下のソースをコピペして、パラメータ(アドレス、ポートなど)設定してからonCreateに他のTextViewみたいに設定するだけで動作する(コピーした時点でレイアウトのxmlのカスタム&~のところに出てくるので普通にレイアウトできる)。はず。
インターネットの許可が必要(忘れてちょっと詰まった)


ゴミコードが混ざっているのはご愛嬌。あくまで個人的なメモなので。
自動生成とかどうでもいいところでないところは太字にしてある



基本的なsurfaceviewのクラスにネットから画像を取ってくるクラスを接続している。サーフェスの更新をマルチスレッドでずっと回してその中でさらにネットから画像を取ってくるスレッドを走らせる。
はっきり言ってバグの温床なのでいろいろ注意、ちなみに強制終了前提




コード

(Class Mysurfaceview.java)


package com.example.jpeg_binaly_client;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.ImageView;
import android.widget.TextView;

public class Mysurfaceview extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //ビューの常時更新には普通のマルチスレッドを使う
    Thread thread;


    SurfaceHolder holder;

    //更新される画像
    public Bitmap tcpbmp;
    //ネットワークスレッドの更新可能シグナル
    public int imggetflag;

    public Mysurfaceview(Context context, AttributeSet attrs ) {//太字の部分がないとxml関連のエラーになる
        super(context,attrs);
        // TODO 自動生成されたコンストラクター・スタブ
        getHolder().addCallback(this);
        this.holder = getHolder();

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO 自動生成されたメソッド・スタブ

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO 自動生成されたメソッド・スタブ
        thread = new Thread(this);
        thread.start();


    }

    @Override
    public void run() {
        while(true){//無限ループ注意

            doDraw(getHolder());

            try {
                Thread.sleep(100);
//通信も絡むので若干待つ、が、ここで待たなくてもいいかも
             } catch (InterruptedException e) {
                // TODO 自動生成された catch ブロック
                e.printStackTrace();
            }
        }

    }


    //描画用関数
    public void doDraw(SurfaceHolder holder) {
       
        tcpbmp =null;
        //画像取得スレッドを走らせる
        tcp_getimage3 jpgg= new tcp_getimage3();
        jpgg.execute("nl");
//特にコマンドなんかが無ければ何でもいい

        int cnt=0;
        int timeout =10;
        int waiting=10;
   //画像取得が終わるまで待つ
        while (imggetflag ==0){
            System.out.println("waiting get...");

            //一定時間待つ
            try {
                Thread.sleep(waiting);
            } catch (InterruptedException e1) {
                // TODO 自動生成された catch ブロック
                e1.printStackTrace();
            }
            cnt += waiting;
            //一定時間待っても更新シグナルがこない場合はタイムアウトとする
            if(cnt>timeout){
                System.out.println("waiting get...timeout");
                break;}
        }

        //更新可能(フラグが出ており、bmpがカラでないとき)なら画像を更新
        if(imggetflag==1 && tcpbmp !=null){
             Canvas canvas = holder.lockCanvas();

             canvas.drawBitmap(tcpbmp, 0, 0, null);
          
            holder.unlockCanvasAndPost(canvas);

        }
        //フラグを0にする
        imggetflag=0;

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO 自動生成されたメソッド・スタブ

    }

    //ネットワークからの画像取得クラス
    public class tcp_getimage3 extends AsyncTask<String,Integer,byte[]>{


        public String ipaddr="(IPアドレス)"; 

        int port_bg = (ポート番号);
       
        Socket socket_bg = null;

        DataOutputStream out=null;
        BufferedReader inp=null;

        DataOutputStream out_bg=null;
        DataInputStream inp_bg=null;
       

        //エラー出力用のカラのバイト列
        public byte[] reterr =null;


        public int cnt =0;
        public int serverflag_bg=0;  //1だとサーバ無しとみなしている。

        public tcp_getimage3(){
            super();
        }



        @Override
        protected byte[] doInBackground(String... val) {
            // TODO 自動生成されたメソッド・スタブ
           
            if (serverflag_bg==0){

                try{

                    try{
                        InetSocketAddress addr = new InetSocketAddress(ipaddr, port_bg);
                        socket_bg =new Socket();
                        socket_bg.connect(addr,50);//タイムアウトの時間は重要、繋がらなかったらさっさと切り上げて再接続したほうが早いので、とりあえず短いほうがいい。でも短すぎると電波環境によっては通信できない可能性もある。

                    }catch(SocketException e){
                        e.printStackTrace();
                        Log.i("network", "socket not connect");
                        return reterr;  //タイムアウトしたらさっさとスレッドを閉じにいく

                    }catch(Exception e){  ///調べたがこの例外じゃないと死ぬ。重要
                        e.printStackTrace();
                        Log.i("network", "その他のネットワーク系エラー");

                        return reterr;

                    }


                    Log.i("network", "sendstart ");
         ///////////////////////////////////////ここからが実質的な処理
                    DataOutputStream out_bg = new DataOutputStream(socket_bg.getOutputStream());

                    ////// データの送信、コマンドなどが無いなら何でもいい
                    out_bg.writeBytes("jpgg");

                    byte[] buf =new byte[1000000]; //最大データサイズ(byte)c++と合わせる

                    int readflag;

                    //データ読み込み↓
                    InputStream is =socket_bg.getInputStream();
                    DataInputStream inp_bg = new DataInputStream(is);

                    Thread.sleep(50);//4バイト受信できるくらい待つ必要がある


                    int receive_size=inp_bg.readInt(); //C++からヘッダ(画像のバイト数)情報を受け取る

                    //画像容量分のバッファを定義する、ここら辺が正確じゃないとBMPがちゃんと生成されない

                    byte[] buffull =new byte[receive_size];

                    //画像データをすべて受信するまで待つ
                    if (inp_bg.available() != receive_size){

                        Thread.sleep(10);
                    }


                    //受信データをバッファに入れる、ループしなくていいかも
                    while(inp_bg.available()>=0){
                        System.out.print("datasize is ");
                        System.out.println(receive_size);

                        //受信データをバッファに入れる。何らかの方法で全部のバッファを受け取ったか確認する必要がある
                        inp_bg.read(buffull,0,receive_size);
                        if(inp_bg.available()==0){break;}

                    };
                   
                    out_bg.close();
                    inp_bg.close();
                    socket_bg.close();


                    //バイナリをビットマップに変換する。jpgも勝手に解析してBMPにしてくれる
                    Bitmap bmplocal=null;
                    bmplocal=BitmapFactory.decodeByteArray(buffull,0,buffull.length);
                    System.out.println(buffull.length);←データサイズと同じか一応比較

                    //グローバルのbmpに変換したBMPを入れる
                    tcpbmp = bmplocal;


                    ///////////////////////////////////////ここまでが実質的な処理
                    //バイナリを出力する。onPostExecuteでの処理はこっちに持ってきても問題ない
                    return buffull;

                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("io err"+e);

                    serverflag_bg=1;
                    return reterr;
                }catch (Exception e) {
                    e.printStackTrace();
                    Log.i("network", "Iilligal ");
                    System.out.println("io err"+e);

                    serverflag_bg=1;
                    return reterr;

                }

            }else{
                Log.v("tcp","noserver");
                return reterr;
            }
        }

        protected void onPostExecute(byte[] result) {

            //呼び出し元のスレッドに描画更新OKのシグナルを出す
            if (result !=null){
                imggetflag=1;}
            else{
                //何らかのエラーでバイナリが無い場合はシグナルを出さない
                imggetflag=0;
            }

            System.out.println("end tcp thread");


        }

    }
}











2013年12月7日土曜日

自分用Vim設定メモ

Vimの設定に関するメモ。主にc++(時々pythonも)でプログラム書くのをイメージ
GVimを(http://www.kaoriya.net/)入手

■ウィンドウズのキーバインドを変えてしまう
無変換キーとか変換キーなんて使わないのでそれぞれEscキーと半角/全角キーに割り振ってしまう。vimではEscキーを使いまくるのでこの変更は重要
↓これで簡単に変更できる(レジストリ変えるので再起動が必要)
http://www.forest.impress.co.jp/library/software/changekey/

■まず以下を vimrc(編集→起動時の設定 で開くファイル)にコピー

nnoremap - $
nnoremap <F11> :make run <Enter>
nnoremap <F5> :!python % <Enter>
set number
set incsearch
set autochdir
set wildmenu wildmode=list:full
copen

ここからメモ
・ノーマルモードで「-」を押すとカーソルが行末に行くこれで0が行頭 -が行末移動になる(隣り合っててわかりやすい)

・F11でメイク→実行 ちゃんとrunができるようにmakefileを書くこと
・ F5でpython実行

・左に行数を表示

・インクリメンタルサーチをオン(いい感じに検索できる)

・set autochdir : 開いているファイルのある場所にカレントディレクトリを移動する。基本的に必須

・ set wildmenu wildmode=list:full : コマンドモード(「:」入力以降)で補間する

・ copen デバッグウィンドウ的なものを開く。便利

■ctags(Exuberant ctags)を使う
主要参考ページ
http://nanasi.jp/articles/others/ctags.html

・ここ(http://hp.vector.co.jp/authors/VA025040/ctags/)でctagsをダウンロード
・exeを環境変数Pathにあるフォルダに入れる
・autochdir がオンになっていれば編集したいファイル開いてGvimのタグ作成ボタンなどでタグを作れる
・宣言と同時に値を入力している変数はタグをつけてもらえない模様

Taglistを入手(参考:http://nanasi.jp/articles/vim/taglist_vim.html)

(GVimフォルダ)\runtime\plugin の中に.vimを \runtime\doc の中に.docをコピーするだけ

・「:Taglist」で画面左もプロジェクトエクスプローラー的なものが出る(eclipseのほうが高性能だが)

2013年11月22日金曜日

windows-mingwでopencv環境構築 走り書き


基本部分参考
http://nenadbulatovic.blogspot.jp/2013/07/configuring-opencv-245-eclipse-cdt-juno.html

これの「mingw32-make」 を「mingw32-make install」にする。

「bin」フォルダにサンプル「c-example-contours.exe」などができていて動作することを確認すること。動作すればここまではOK


サンプル参考 : これが動作すれば環境構築完了
 http://stackoverflow.com/questions/10860352/getting-started-with-opencv-2-4-and-mingw-on-windows-7

上記binフォルダを環境変数 PATHにしておくこと

 -I オプションは "C:\(opencv)\build\include" opencv解凍したらbuildフォルダがある(mingwの場合使用しなさそうだから見逃した)。その中のincludeを-Iとすること他のincludeとかでは適切な階層にヘッダが無いことがある。

-Lは cmakeで生成されたopencvの「lib」フォルダ、-lオプションのバージョンもそのまま写さないように注意

これで最新バージョン(2.4.7)動作確認。細かい変更やらなにやら(解凍後の「build\x86」にmingw向けのbinが無いなど)で以前と勝手が異なっており1日つぶしたのでメモ 
 

2013年10月27日日曜日

ラジコン(の様な何か)できたよ~ ヽ(゜ω゜)ノ Ⅱ

ここ数ヶ月の休日をドブに捨てて何とかラジコンらしきものができてきたのでうp

↓概観。臓物を乗せただけ。これからボーナスをはたいた3Dプリンタが役に立つはず・・・

















↓コントローラ。androidにカメラ映像を出すのが鬼門だった。神が作りたもうたライブラリを導入することでなんとか解決。

















↓操作風景(コントローラ)、無駄にマルチタッチなどを使用。
ちなみにソナー(ではないが距離がわかるセンサ)とかコンパス、ジャイロのボタンなんかは未実装・・・(´・ω・) 一応単体動作は確認済みだけどまたそのうち実装すればいいよね・・・どうせコマンド送信するだけだし。。

 

 ↓操作風景(ラジコン側)。↑の動画を見てもわかるように操縦系がやや糞仕様。これでもだいぶましになったほうだが。

 

※諸事情(部屋の散らかり具合)により狭いエリアでの撮影となっております。







2013年10月20日日曜日

android(SimpleMjpegView)でwebcamからのストリーミングを見たときのメモ

休日2日(18時間程度)つぶしてどうにかウェブカメラ映像をタブレット上で見れるようになってのでその際のメモ、公開されているライブラリを取り込んだだけだが・・・

一応ライブラリ(SimpleMjpegView)のライセンスに基づく告知

This software is based in part on the work of the Independent JPEG Group.
Moreover, for decoding MJPEG, some codes in OpenCV are utilized.
Therefore, before downloading, you have to agree to the following license.
//////////////////////////////////////////////////////////////////////////////////
IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
By downloading, copying, installing or using the software you agree to this license.
If you do not agree to this license, do not download, install,
copy or use the software.
                    Intel License Agreement

            For Open Source Computer Vision Library
Copyright (C) 2000, Intel Corporation, all rights reserved.
Third party copyrights are property of their respective owners.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
  • Redistribution's of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  • Redistribution's in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.
  • The name of Intel Corporation may not be used to endorse or promote products
    derived from this software without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are disclaimed.
In no event shall the Intel Corporation or contributors be liable for any direct,
indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused
and on any theory of liability, whether in contract, strict liability,
or tort (including negligence or otherwise) arising in any way out of
the use of this software, even if advised of the possibility of such damage.

 ライセンス告知ここまで


参考文献:
1、 neuralassemblyのメモ 様:
http://neuralassembly.blogspot.jp/2012/12/androidwifimjpeg.html
今回はTakashi Kanamaru 氏が公開されているありがたいライブラリ「SimpleMjpegView」を自分のプロジェクトに使う。
 
2、yahoo知恵袋 Android NDKを使った開発環境の構築
http://note.chiebukuro.yahoo.co.jp/detail/n136598
今回の話は実質的にNDKに関するものなので重要。


・今回の目的 : 自作androidアプリに(ゲームボーイ的な感じで)ウェブカメラからのリアルタイム映像を映す

・手法 : 数日粘ったが自分の手には負えそうも無かったので参考文献1のライブラリを利用させて頂くことに

・利用させて頂き方 : 
※事前にNDKが使える状態になっていること

1、 https://bitbucket.org/neuralassembly/simplemjpegview からライブラリをダウンロード、展開→eclipseへプロジェクトをインポート

2、インポートしたプロジェクトが正常に動く(ウェブカメラのストリーミングが見られること)ことを確認する


3、問題なければMjpegView.java と MjpegInputStream.javaを自分のプロジェクトのsrcにコピー。jniフォルダもコピー(自分は他にndkを使っていなかったから問題なかったが自作ndkがある場合はmakeファイル内容などを上手くマージしたりする必要があるかもしれない)

4、 コマンドプロンプトで自分のプロジェクトのフォルダに移動(cd (プロジェクトフォルダパス))して「C:\\android_ndk\\android-ndk\\ndk-build.cmd(筆者の場合)」を実行。要はjniフォルダ内のcファイルをビルドする。旧バージョンだとcygwinからやらないといけないようだが参考文献1のリンクなどを参照のこと

※重要 : ここで半日詰まった。参考文献2にもあるがjniフォルダ内のcファイルとヘッダファイル内の一部を自分のプロジェクト名に(規則にのっとって)改名する必要がある。↓
「JavaとC/C++(或いはその他の言語)によるプログラムの間で相互に連携をするためには、JNI(Java Native Interface)という仕様に沿う必要があります。この仕様では、C/C++側の関数名は頭にJava_を付け、その後ろにパッケージ名、クラス名、 メソッド名を並べ、ドットをアンダーバーに置き換えた名前にします。」(参考文献2より引用)

コピーしてきた時点でcファイルとヘッダファイル内の関数名はインポートしたSimpleMjpegViewプロジェクトのパッケージになっているので自分のパッケージ名にすべて変更する。これをしないとリンクエラーが発生する(そんなnativeメソッド無いよ。というエラーが発生する)。まとめて変更するための情報が参考文献2にあるが今回は手動で変換した

5、インポートしたプロジェクト内のMjpegActivity.javaのソースを参考に必要な部分を自分のライブラリに書き足す。最低限以下の部分をコピペすれば動作はする(ポーズなどで落ちるが)
5-1 onCreate内
     mv =(MjpegView) findViewById(R.id.mjpegView1);
         if(mv != null){
                mv.setResolution(640,480);
            }
            new DoRead().execute(URL);

 5-2 activityクラス内、onCreateなどと並列に
    public class DoRead extends AsyncTask<String, Void, MjpegInputStream> {
        protected MjpegInputStream doInBackground(String... url) {
            //TODO: if camera has authentication deal with it and don't just not work
            HttpResponse res = null;
            DefaultHttpClient httpclient = new DefaultHttpClient();
            HttpParams httpParams = httpclient.getParams();
            HttpConnectionParams.setConnectionTimeout(httpParams, 5*1000);
            Log.d(TAG, "1. Sending http request");
            try {
                res = httpclient.execute(new HttpGet(URI.create(url[0])));
                Log.d(TAG, "2. Request finished, status = " + res.getStatusLine().getStatusCode());
                if(res.getStatusLine().getStatusCode()==401){
                    //You must turn off camera User Access Control before this will work
                    return null;
                }
                return new MjpegInputStream(res.getEntity().getContent());
            } catch (ClientProtocolException e) {
                e.printStackTrace();
                Log.d(TAG, "Request failed-ClientProtocolException", e);
                //Error connecting to camera
            } catch (IOException e) {
                e.printStackTrace();
                Log.d(TAG, "Request failed-IOException", e);
                //Error connecting to camera
            }
            return null;
        }

        protected void onPostExecute(MjpegInputStream result) {
            mv.setSource(result);
            if(result!=null) result.setSkip(1);
            mv.setDisplayMode(MjpegView.SIZE_BEST_FIT);
            mv.showFps(false);
        }
    }


結果:
そりゃあ所詮無線のカメラなので若干のラグはあるがかなりスムーズ。Takashi Kanamaru 氏は間違いなく神



以上。結局NDKの基本的なところが全くわかっていなかったのが時間をとった原因だったり・・・



2013年10月13日日曜日

秋月サーボの大事な覚書き(arduinoなんかで使う際の設定値)

みんな大好き秋月サーボ(GWSサーボ)の使い方についてのメモ

参考文献:
1、GWS(サーボの会社)にあるサーボのアプリケーションノート(初見だと場所がわかりにくい)
http://www.gws.com.tw/english/product/servo/sat%20form.htm

2、Arduino 日本語リファレンスのservoのページ

要点 : 秋月で売ってるGWSのサーボを使うときはServoライブラリのデフォルトのパルス幅(min:544、max:2400)を使ってはいけない。特にmin側はぶっ壊れるかもしれないので注意

参考文献1によるパルス幅:

0.8~2.2 マイクロ秒 (800 ~ 2200ms)

※以下、個人的なメモ
 
筆者用設定値(スペック超えているため多分サーボが壊れる。自己責任で・・というよりこの設定を使用してはいけない):

GWSMICRO/2BBMG/F (小さめの1500円のやつ) → 800~2500
・筆者環境でこの設定じゃないと180度回らなかったので破損覚悟で設定。

GWSPIC+F/BB/F(最小サイズの900の方) → 800~2200
・そもそも180度回らない。目視で120度くらいか。2500で異音がしたのでスペック値に

以上

2013年8月19日月曜日

夏休みの工作 ~pythonにおけるセンサ出力値のビット演算~

大した話ではないけど半日詰まったのでメモ。


I2Cなんかから1バイト×2(Hbit、Lbit)のような形でデータを受け取って数値に変換するときの注意点。

例:
Hbyte : 11101110
Lbyte : 00011111

→2つあわせて 1110111000011111 を作ってから数値に変換する。Hの方を左に8bitシフトしたり256を掛けたりしてから足す(あるいはHとLのorをとる)

特にこのあたりでは問題は無いが、2の補数を取って負の値を出したいときpythonでは正常に処理できない場合がある。

例:
H : 10000000
L : 11001100

あわせて 1000000011001100(符号付10進数で-32564) となる。この値をそのままpythonで扱うとpythonの中では32972として扱われる(符号なし扱いのため)。何bitの符号付整数か(どのビットが1だと負扱いなのか)がpythonに認識できないので当然こうなるといえばこうなる。しかしpythonに例えばこのビット列は16ビット符号付整数として扱ってね、とやる方法もちょっとわからなかったので普通にxorを使って2の補数を算出する処理を実装する。これで何ビット整数として扱わせるかもこちらで指定できる。

(プログラムのイメージ)

if H & 0b1000000=0b10000000:   ←Hバイトの頭が1(負数)だったら。ここの数値の桁数でビットを指定している。

   val= (H <<8) | L ←HとLを合体 1000000011001100
   val_inv=val ^ 0b1111111111111111 ←「^」はxor(pythonでは累乗は**なので注意)16bitの全部1の値とxorをとってすべてのビットを反転する。
  val_inv = val_inv+1    ←1足して2の補数にする。この時点で正しい値の絶対値になっている
 out =val_inv*(-1) ←マイナスをつける

以上。これで上の例だと outが-32564になる。
面倒だけどこれをやらないとpythonでは符号付出力のI2Cセンサがまともに使えないので地味に重要。

2013年8月10日土曜日

夏休みの工作 ~python-arduino連携でI2Cを辛うじて使う(今日の分)~

センサーとかモータードライバ(まだ碌なのがないが)を少ないPIN数で使うためにI2Cが必要だったのでありがたい外部ライブラリを勝手に拡張してみた。

今回の参考資料:Tristan Hearn氏が作ってくださった以下のライブラリ。どうやらNASAの方でお勤めの方っぽい((((゜Д゜;))))。ちなみにライブラリはMITライセンス。
 ↓入手場所
https://github.com/thearn/Python-Arduino-Command-API

↓MITライセンスに基づく告知(ここから)
Copyright (c) 2012-2013 Tristan A. Hearn <tristanhearn@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

↑(ここまで)

今回は上記ライブラリのソースを勝手に弄ってしまうお話です。MITライセンス万歳。

↓python側のプログラム。これを自分のプログラムの上のほうに差し込んでおきます。要上記ライブラリとそれを動かすのに必要なもの

from Arduino import Arduino
class Arduino_C(Arduino):
    def __init__(self,baud=9600,port=None,timeout=2):
        Arduino.__init__(self, baud, port, timeout)

    def i2c_write_1(self,device,data):
        print "i2cwrite"
        cmd_str=''.join(["@i2cw1%",str(device),"%",str(data),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")
        print "i2c return",hex(int(rd))
    def i2c_write_2(self,device,data1,data2):
        print "i2cwrite"
        cmd_str=''.join(["@i2cw2%",str(device),"%",str(data1),"%",str(data2),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")
        print "i2c return",hex(int(rd))

    def i2c_read_1(self,device,location):
        cmd_str=''.join(["@i2cr1%",str(device),"%",str(location),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd = self.sr.readline().replace("\r\n","")

        output=int(rd)
        return output

    def i2c_read_2(self,device,location):
        cmd_str=''.join(["@i2cr2%",str(device),"%",str(location),"$!"])
        try:
            self.sr.write(cmd_str)
            self.sr.flush()
        except:
            pass
        rd1 = self.sr.readline().replace("\r\n","")
        rd2 = self.sr.readline().replace("\r\n","")
        output=[int(rd1),int(rd2)]
        return output

↑筆者の頭脳とやる気の関係で1バイトまたは2バイトの書き込み、読み込みの4つの関数を追加しただけ、せめてバイト数ぐらいは可変にすべきだが・・・しょうがないよね、夏風邪引いてるしね。

引数のdeviceとかdata、locationとかは16進数(pythonでも0x00表記)でやると頭で変換しなくていいから楽。

↓以下、arduino側のソース(prototype.ino)に追加した部分のその1。serial_parser関数より上のどこかに差し込む。

void i2c_write_1b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int wdata = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(wdata);
    Wire.endTransmission();
    Serial.println(id);
}

void i2c_write_2b(String data){
    String sdata[3];
    split(sdata,3,data,'%');
    int id = Str2int(sdata[0]);
    int wdata1 = Str2int(sdata[1]);
    int wdata2 = Str2int(sdata[2]);
    Wire.beginTransmission(id);
    Wire.write(wdata1);
    Wire.write(wdata2);
    Wire.endTransmission();
    Serial.println(id);
}

void i2c_read_1b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int location = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(location);
    Wire.endTransmission(false);
    Wire.requestFrom(int(id),1,false);
    int out1 = Wire.read();              
    Wire.endTransmission(true);
    Serial.println(out1);
   
}

void i2c_read_2b(String data){
    String sdata[2];
    split(sdata,2,data,'%');
    int id = Str2int(sdata[0]);
    int location = Str2int(sdata[1]);
    Wire.beginTransmission(id);
    Wire.write(location);
    Wire.endTransmission(false);
    Wire.requestFrom(int(id),2,false);
    int out1 = Wire.read();              
    int out2 = Wire.read();
    Wire.endTransmission(true);
    Serial.println(out1);
    Serial.println(out2);
}

↑ここまで

↓その2。serial_parserのcmd==(コマンド)のelse ifが続いているあたりに自然に差し込む。

else if (cmd =="i2cw1"){
   i2c_write_1b(data);
  }
    else if (cmd =="i2cw2"){
   i2c_write_2b(data);
  }
  else if (cmd =="i2cr1"){
   i2c_read_1b(data);
  }
  else if (cmd =="i2cr2"){
   i2c_read_2b(data);
  }

↑ここまで

明らかに命令の種類が足りてないような気がするが、ストリナとかで売ってるようなジャイロとかならこれである程度読み書きできるはず。読み込んだデータバイトの処理(2つのバイトを接続したりとか単位換算とか)なんかはpython側で。

いつかHearn氏が正規(?)のI2C実装をしてくれることを祈りつつ今回は終了。

P.S : prototype.ino のsetup() 内に Wire.begin(); を入れないと何も機能しないので注意(10分ほどはまった)

2013年7月29日月曜日

メモ:いつものI2C

ArduinoでI2Cを使うときによく使うコピペ用

↓ここから
//書き込み
Wire.beginTransmission();
Wire.write(0x);

Wire.endTransmission();


//読み込み、パターンはデータシート確認のこと
Wire.beginTransmission();
Wire.write(0x);
Wire.endTransmission(false);
Wire.requestFrom(int(),1,false);
output = Wire.read();

Wire.endTransmission(true);

//連続読み込み(6つのレジスタから1byteずつ読み込む場合)
byte addr[6]={0x,0x,0x,0x,0x,0x,};
byte read_data[6]={0,0,0,0,0,0};   //2byte以上のときは2次元配列に

for(int i=0;i<6;i++){
Wire.beginTransmission( );
Wire.write( addr[i] );
Wire.endTransmission(false);
Wire.requestFrom(int( ),1,false);
read_data[i] = Wire.read();   
Wire.endTransmission(true);
  }

//データを8ビットシフトして他のbyteとつなげる。便利

int hl_joint(byte high,byte low){
  int high_data = int(high) << 8;
  int output = high_data | int(low);

  return output;
}

//レジスタ6つのデータを読み込んで繋げて出力する関数。3軸センサーに。
//devはデバイスのアドレス、xyzは出力を入れる配列(出力の型は自由)
void read_xyz(byte dev,int xyz[]){

byte addr[6]={0x,0x,0x,0x,0x,0x};
byte read_data[6]={0,0,0,0,0,0};   //2byte以上のときは2次元配列に

for(int i=0;i<6;i++){
Wire.beginTransmission(dev);
Wire.write( addr[i] );
Wire.endTransmission(false);
Wire.requestFrom(int(dev),1,false);
read_data[i] = Wire.read();   
Wire.endTransmission(true);
  }
// read_data[0]=Low_byte ,read_data[1]=High_byte・・・の場合

for(int j=0;j<3;j++){
 xyz[j]=hl_joint(read_data[2*j+1],read_data[2*j]);
}

}

とりあえず以上

2013年7月15日月曜日

夏休み前の工作(2) ~ArduinoでI2Cモータドライバを動かしたときのメモ~

秋月電子とかストロベリーリナックスで売ってるDRV8830を使ったモータードライバモジュールをかろうじて使えるようになったのでそのあたり+ArduinoでI2Cを使うにあたっての自分用メモ

この記事の内容によって生じたことの責任は取りかねますのであくまで自己責任でお願いします。


1、重要な参考文献: mixture-art@Q 様の記事(「Arduino I2C」でぐぐるとトップに出る)

http://mixture-art.net/arduino%E3%81%A7i2c%E9%80%9A%E4%BF%A1%E3%82%92%E3%82%84%E3%82%8B%E9%9A%9B%E3%81%AE%E3%83%A1%E3%83%A2/

↑参考にしないとI2C用ライブラリ(Wire)で適切なデータ受信ができなくなる。じぶんでC言語とかで解決するハメに・・・

2、DRV8830の日本語データシート(ストリナのDRV8830ドライバのページにリンクがある)

↑見ないと使い方がわからない(秋月のドライバのトリセツは情報が不十分)


とりあえず以下が最低限動くソースとコメント↓

ArduinoIDEに最初から入ってる 「master_writer」をベースに改造

#include <Wire.h>

byte m_driver=0x64;  ←A0 A1をオープンにしてるとドライバのアドレスは
「1100100」=「01100100」→16進数にすると「0110(←6) 0100(←4)」=64(HEX) となる。重要

↓モータの速度を設定する関数。適当
void setmotor(byte val,byte ori){  ←モータ出力設定(VSET)とブリッジ制御用の2ビットを入力
  Serial.print("setmot start\n");
  Wire.beginTransmission(m_driver); ←「書き込みモード」でドライバと通信開始。この命令を使うと勝手にアドレス(7桁)の末尾に「0(Write)」が加えられて通信される(長期間詰まった)
  Wire.write(0x00);  ←サブアドレス。レジスタ0(アドレス:0x00)のアドレスを送信
  byte sp6=val << 2; ←引数valを2ビット左にシフト
  byte mot = sp6|ori; ←方向の2ビットとorをとる。これで送信用のデータバイトができる。  Serial.println(mot,HEX); ←なんとなくシリアルに出力
  Wire.write(mot);  ←データバイトを送信。ちゃんと通ればモータが回ったりする。

  Wire.endTransmission();    ←通信終わり。「STOP」ビットをドライバに送信する。 
}

void fault_clear(){ ←faultレジスタ(0x01のレジスタ)内の障害状態をリセットする。フェールの種類によってはリセットしないとドライバが使用できない場合がある。
  Serial.print("fault_clear\n");
  Wire.beginTransmission(m_driver);
  Wire.write(0x00);       
  Wire.write(B10000000);     ←第7ビットに1を書き込むとフェールのログが消える。
             
  Wire.endTransmission();

}

void setup()
{
  Wire.begin();
 
  Serial.begin(9600);
  Serial.println("\n set ");
  Serial.println(m_driver,HEX);
 
  fault_clear();    ←最初に一応フェールをリセットしておく
}


ここからメインループ
void loop()
{
↓適当にモータを動かしてみた。第1引数は0x3fあたりが最大、第2引数は0x0,1,2,3のどれかを入力。詳しくはデータシート参照。待ち時間は別に2秒とかじゃなくていい(多分不要)
 delay(2000);
 setmotor(0x1E,0x0);
 delay(2000);
 setmotor(0x1E,0x0);
 delay(2000);
 setmotor(0x3E,0x0);
 delay(2000); 
   

↓ここからデータ読み出し。非常に重要な箇所あり(知らないとI2Cデータ受信できない)

  Serial.print("0x01 read start\n");
  Wire.beginTransmission(m_driver);  ←受信だけど書き込みモードでデータ通信開始、ドライバの仕様。
  Wire.write(0x01);        ←読みたいレジスタのアドレス
  Wire.endTransmission(false); ←参考文献より。これを入れないとドライバとの通信が維持できない。

  Wire.requestFrom(int(m_driver),1,false);  ←読み込みモードで通信開始。こちらは勝手に末尾に「1(read)」が付け加えられる。ドライバのマニュアルに「アドレス(書き込み)、アドレス(読み込み)」とか親切に書いてくれているが、そのアドレスは末尾の0,1を加えたアドレスなのでそれをこことかbeginTransimission()に入れても通信できない。こんな馬鹿なところでハマってしまってドライバを数ヶ月放置してた・・・(つд;)

  byte out = Wire.read();     ←普通にread。
  Serial.println(out,HEX);  ←シリアルに出力して内容を確認

  Wire.endTransmission(true);    ←通信終了。()内のtrueは必要。これも参考文献より
  delay(2000);

↓レジスタ0も同様にreadしてみる。
 Serial.print("0x00 read start\n");
  Wire.beginTransmission(m_driver);
  Wire.write(0x00);       
 Wire.endTransmission(false);
  Wire.requestFrom(int(m_driver),1,false);
  byte out2 = Wire.read();              
  
  Serial.println(out2,HEX);

  Wire.endTransmission(true);   
  delay(2000);
}










あと、配線について。電流センス抵抗に適切な抵抗をつないでGND接続しないといけないが、制限とか気にしないのであればここはGND直結でいい(直結(か小さい抵抗をGND接続)しないとドライバが動作しない。注意)。Faultnのピンは何も接続しなくても動作した。

以上。上記の情報でモータを動かせる・・・はず。動作確認したが動かせるモーターと動かせないモータがあった(ドライバのせいでは無く、多分電流流れすぎか何かで何らかの制限がかかってると推測)

 今回ArduinoのWireライブラリの親切設計(自動的にW,Rビットを付加)についてよく理解してなかったのがひどかった。しかしこれで他のI2Cデバイスも普通に使えるようになるはず( ´д`)b

2013年7月13日土曜日

夏休み前の工作(1)

今回つくったのはこちら↓
















 なんてことはないただのUSBハブですが、USBの線が2つ出ています。
片方は電源用、片方は信号用になっており、スマホ用USBチャージャーなんかから
電源を取れるようにしてあります。 これで電源用のインターフェースをUSBに統一できる。

















 つなぎ方(重要) : 試行錯誤があったが以下のように配線します。

1、元から繋がってるV+線を切断、こっちはオープンにしておく
2、 基板のV+と外部電源用電源V+線(写真手前赤線)を半田付け
3、外部電源用GNDを基板GNDに半田付け、元からある線と一緒につける(GNDを共通にする)
4、外装に適当な穴を開ける
5、いろいろ絶縁とか、ショートしないように注意


















Raspberrypi とかPCでも一応正常動作を確認(なんとなく不審な挙動がみられたけど・・・)
よい子は真似しないでね。

~おわり~

2013年6月9日日曜日

ラジコンできたよー(・ω・)ノ Ⅱ  ~android - raspberry pi - arduino を使った入出力~

最近なかなか気が向かずに放置していたラジコンを有給とった勢いで一通り作ってみました。

早速現物を↓
 
←上から。必要なデバイスを無駄なく筐体に搭載する。筐体は軽量かつ低コストな素材を使用・・・どう見ても空き箱にぶち込んだだけ(´;ω;`)。


←斜め。前方にwebカメラ(ヨドバシで500万円)を搭載。


←正面。下半分は前回同様タミヤのやつ。そろそろキャタピラがぼろくなってきた。多分呼ばないがラジコンの名前をSAGAWAと命名。












↓動作のようす。

 

片手がカメラでふさがっているので操作がひどいことになっていますが、今のところ
・シークバーで速度調整(後退も可)、
・左右に(全力で)傾けるとそっちに曲がる。
・タブレットに車載カメラの映像が映る(10~15fps)。
機能を実装。
(片手で撮影して無ければ)どうにかラジコンとして遊べます。

とりあえず動いてしまえばラジコン自体は割とどうでも良くて、各デバイス間のプロトコルとかソフトの方が個人的に重要だったりします。ここをしっかり作っとけば後でヘリでもロボットでも(対応したヘリとかロボットを作りさえすれば)動かせるので。

でも今日は疲れたのでそのへんのメモはまた来週。



2013年5月8日水曜日

明日のためのメモ pythonでバイナリ

pythonを使ってバイナリデータ(長さ不定のヘッダあり)を変数に読み込み特定の文字ごとに区切ってリストに入れて、区切られた要素はときどき要素数が一定でないのに対応して任意のパターンでアンパックしたいときの処理がしたいなー( ´・ω・`) というときに使えそうなプログラムのメモ

こんなバイナリ入力→「****123*****123*****123**123***」

を↓こんな感じで出力、output[]の中身を一行ずつCSVに入れたりすると幸せになれるかもしれない。

output=[[aaaa],[123,100,200],[123,20,30],[123,34,12,12],[123,0,2]]

関数にしてみた↓ conは入力(バイナリ)

def binparse(con):
    con=con.replace('123','\n123')
    con=con.split('\n')
    print "replaced\n",con
    cnt=0
    for i in con:
        if 1<cnt<(len(con)) and i[0:2] !='ab':  ←配列の修正。区切り文字が意図しないところにはいっていてもこれでたぶん修正できる。
            con[cnt-1]=con[cnt-1]+"\n"+i
            con.pop(cnt)
       
        cnt += 1

    out=[]
    cnt=0
  
    fmt1='2c6hi'  ←文字数ごとに適当なフォーマットを設定。パターンが増えそうだとインデックス
    fmt2='2c3hc'   とかリストにまとめたほうがいいかも
    for i in con:
        l=len(i)
        if l==20: ←長さが20だったら~fmt以外は同じ処理
            tmp=struct.unpack(fmt1,i)
            tmp2=[]
            for j in range(len(tmp)):
                           
                tmp2.append(tmp[j])
            print out
            out.append(tmp2)
        elif l==9:
            tmp=struct.unpack(fmt2,i)
            tmp2=[]
            for j in range(len(tmp)):
                           
                tmp2.append(tmp[j])
            print out
            out.append(tmp2)
            pass
        cnt += 1
   
    f.close()
    return out

2013年5月6日月曜日

GWの工作(2) ~androidとpythonのソケット通信に関するメモ~

raspberry piというたのしいおもちゃを買ってしまったのでxbeeは投げ捨ててwifiを使った通信を試みる今日この頃。今回はその辺のメモ

・今回はデータをandroidタブレット→(wifi)→raspberrypi(python)→(シリアル)→arduino の経路で流すシステムを考えます。

・pythonをソケットのサーバ、androidをソケットのクライアントとします。

■まずpythonのソケット通信から、これは以前もやったのでわりとスムーズだった。

 通信用のスレッド(抜粋)↓ ※メインのスレッド(main())が回ってないとおかしいことになる。main()は適当に無限ループにしておく

class tcpthread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        host = '○○○.○○○.○○○.○○○'  ←IPアドレス
        port =(ポート番号)

        tcpcom=0   ←ポート処理用のフラグ(後述)

       ↓ソケットのインスタンスを作成
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        while tcpcom==0:   ←ポートが詰まってたら別のポートを探す処理(何度もテストしてるとたまにポートが詰まる。時間経過でまた開くが面倒なので別のポートをスキャンする)
            try:
                s.bind((host,port))
                tcpcom=1   ←ポートがあったらフラグを立ててループ脱出
                print "connection port:",port  ←つながったポートを表示。android側はこれを入力             except:
                port =port +1 ←詰まってたら次のポートへ

    s.listen(1) ←受け付けるクライアントは1つのみ

    ↓通信ループ
    while True:
            print "waiting"
            conn,addr=s.accept()  ←クライアントからの接続待ちつながるまでここで止まる
            print "accept port :",conn,"addr :",addr   ←つながったIPを表示
            data=conn.recv(120)       ←クライアントからデータ受け取る()内は受け取るバイト数

      conn.send("send python\n")  ←クライアントにデータ送信


    //s.close() ←ソケットのクローズ。通信ループの外にあることに注目。今のところソケットは開けっ放しである。


 if __name__ == '__main__':
    tcp= tcpthread()
    tcp.setDaemon(True)  ←スレッドを綺麗に終わらせる呪文。
    tcp.start()
    time.sleep(1)
    main()


■次にandroid。初心者だったので辛かったが以下の参考資料などを参考にどうにか実装
http://yamato-iphone.blogspot.jp/2012/06/blog-post_5123.html

まずマルチスレッドのやり方。AsyncTaskというクラスを使うのが正道っぽいがなんだか面倒くさそうだったのでもっと楽なやり方(ハンドラを使った普通のマルチスレッド)で実装
public class MainActivity extends Activity {

↓(実質的な)グローバル変数
private Handler mHandler;  ←ハンドラを作っておく
 serverflag =0;  ←サーバーがあるかどうかのフラグ。後述

↑ここまでグローバル変数
protected void onCreate(Bundle savedInstanceState) { 

~略~

mHandler =new Handler(); ←ハンドラのインスタンス作成

/////マルチスレッド、ハンドラを使っているからGUI書き換えもできる。onCreateの中に書いてしまっている
        (new Thread(new Runnable() {

            @Override
            public void run() {
                    while(true){
                         mHandler.post(new Runnable(){
                             @Override
                             public void run(){
       ↑ここまでほぼ呪文。普通のマルチスレッドの中でハンドラを経由した処理を実行している(と思う)。GUIの更新が絡む処理がこのrun()のなかでやること。でないと例外が出る仕様
                                 socketcom("test")    ←後述するソケット通信関数
                                 textview1.setText(Integer.toString(”文字”));
                                 System.out.println("マルチスレッド 通過");

                                


                             }
                             });
                       
                        try { ←マルチスレッドのtry

                                    Thread.sleep(30); ←ウエイトを設定。別にここじゃなくてもいい。通信が絡む場合はウエイトが必要
↓以下も呪文
                            } catch (InterruptedException e) {}
                    }
            }
    })).start();
        ///////ここまでマルチスレッド

○ここからソケット通信の話↓ というか自分用テンプレート関数

public void socketcom(String str){ ←引数は別になんでもいい。無くてもいい
if (serverflag != 1){   ←サーバ無かったら以下の処理素通り
    try {
        //タイムアウトを設定。この辺は重要。タイムアウトを手動設定しないと通信途絶と同時にアプリも止まる。socket =new Socket(IP,PORT) で一発接続しないのがコツ


        InetSocketAddress addr = new InetSocketAddress(ipaddr, port); ←とりあえずアドレスだけ専用のインスタンスにまとめておく。あと当然だがアドレスとポートはサーバのものを設定する
        socket =new Socket(); ←カラのソケットを作る
        socket.connect(addr,500); ←ソケットを接続。引数の数字はタイムアウト時間(ms)。時間はお好みで
       
↓ 入力と出力のストリームを作っておく。ほぼ呪文。生の通信データ(byte)を使いやすい形に変換しているためなんか複雑になっている模様
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());

        BufferedReader inp = new BufferedReader(new inputStreamReader(socket.getInputStream()));

↑入出力定義

         out.writeBytes(str);  ←ソケットへの出力。pythonに届く

        String responseLine="";    入力を入れる変数。↓のifの中で作ってもいいかも

        if ((responseLine = inp.readLine()) != null) { ←「何かサーバから来てたら(呪文)」
                //System.out.println("Server: " + responseLine+cnt);
                textview.setText("Server: " + responseLine); 来たデータを任意に処理
            }else{

                System.out.println("null in");
            }

            out.close();   
            inp.close();
        socket.close();  ←クライアント側はサーバと違って開けっ放しにしない。一通信ごとに閉じる。開けっ放しにしようとして半日つぶした(個人的には開けっ放しにしたいが・・・)。



         } catch (UnknownHostException e) { ↓勝手に生成される例外処理IOのほうは重要
            e.printStackTrace();
            System.out.println("UnknownHost err"+e);
             textview.setText("Server:none (unknownhost)" );
            return;
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("io err"+e);
            tv.setText("Server:none (IO) " );
            serverflag=1;     ←「サーバー無し」のフラグを立てる。後から別のボタンとかでこれを0にすると再接続できる(実装してないから予想)
            return;
        }

}else{
    tv.setText("サーバーが見つかりませんでした " ); ←サーバ無かったときの処理。適当に。

}

androidのソケット通信ここまで

おまけメモ : ~とりあえずボタン~ ちょっとボタンがいるときに

public class MainActivity extends Activity { Button bt;
protected void onCreate(Bundle savedInstanceState) {

bt = new Button(this);
bt.setText("button");

(レイアウト).addView(bt);
bt.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                

                (ボタンが押されたときの処理)
               
            }
       });


~androidでwebカメラの映像を見る~ ここも死ぬほど詰まった。どうにもならず結局外部ライブラリ(MjpegView Class)を使うことに(GNU GPL v3 注意)。
使い方抜粋(大きさ固定の仕方)
mjv=new MjpegView(this) {//表示の大きさを固定にする
             @Override
             protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

              super.onMeasure(widthMeasureSpec, heightMeasureSpec);

              setMeasuredDimension( 320 , 240 );
             }
            };
mjv.setSource(MjpegInputStream.read("http://(IPアドレス):(ポート)/?action=stream.mjpeg"));


以上。これでとりあえずandroidからpythonにデータを送れる(wifiでも)、しかもraspberry piにつないだwebカメラの映像もandroidで見れる(raspi側で mjpeg streamer などが必要)。
参考資料
http://linux.yebisu.jp/memo/800

これであとはpython-arduinoのプロトコルを作ればめでたくAndroidでコントロールできるカメラつきラジコンが出来上がる・・・予定。テストプロトコルでモーター動かすだけで満足してしまっている自分が怖い

2013年4月29日月曜日

GWの工作(1) androidとarduino(ややこしい)を無線でシリアル通信

~GWは東京観光でもしようかと思っていたが気がついたらまた電子工作に入れ込んでいたでござる の巻~

最近流行りのタブレットでArduino Microをとりあえず動かしてみました。今回はそのあたりのメモ

~材料~

nexus7 : android 3.0以上くらいなら大体okだと思う
USB ホストケーブル: 片方はスマホに挿すやつ(usb-miniB)、もう片方が普通のUSB(メス)になってるやつ
arduino micro : 別にunoでもなんでもok シリアル通信するだけなので
xbee 2個 : ZBの普通のやつ(あとUSBドングルとブレッドボード変換基板)。使い方は過去記事参照のこと

 ~使ったソフト~
 Arduino1.0.4 :これじゃないとボードにmicroが入ってない?
神が作ってくださったandroid usbシリアル通信ライブラリ「FTDriver」と「Android USB Serial Monitor Lite(google playで入手可)
参考 : http://ksksue.com/wiki/doku.php?id=wiki:android:hardware:usb:ftdriver

~参考資料~
「xbeeで作るワイヤレスセンサーネットワーク」:オライリーの本。一家に一冊は欲しいね。

nexus7のUSBドライバーのインストールについて。
・今回の内容ではいらんけど、これがないとPCで書いたeclipseがnexus7認識しないので注意。あと「タブレット情報」ボタンを7回連打するのも忘れずに。他にも入れ方は色々あるようだがとりあえず公式からのインストールのやり方。
http://blogs.yahoo.co.jp/momo_poem/67257950.html


やり方
0、Xbeeの設定をする(親機をUSBドングル、子機をarduino)やり方は参考資料とか過去記事を

1、nexsus7でAndroid USB Serial Monitor Liteを入手

2、nexsus7にUSBホストケーブルとxbeeドングル(親機)を接続・・・できるようにしておく

3、 arduinoとXbeeの配線 
※注意:arduinoのTX出力が5VなのでXbeeのRXとの間で3.3Vに降圧すべき。でないとxbeeぶっ壊れる こんな便利なものがあるみたいです→http://www.switch-science.com/products/detail.php?product_id=1216
  ・arduino の3.3VとxbeeのVin
  ・arduinoのTXとxbeeのRX
  ・arduinoのRXとxbeeのTX
  ・arduinoのPIN2とGNDにLED(抵抗入り)
・・・筆者のXbee(子機)はもうだめかもわからんね

4、arduinoのプログラムを書く。シリアルで’a’を受信したらLED ON’b’を受信したらOFFするプログラムを書いただけ。
要点:
Arduino microでシリアル通信するときはSerial1クラスを使うこと(3時間くらい詰まった) 例:Serial1.begin(9600)
なんか普通のSerialのほうはPCとの接続に使ってるみたい。Arduino Leonardも同様

5、nexus7にホストケーブルを挿してAndroid USB Serial Monitor Liteを起動

6、プログラムアップロード。この時点でもnexus7からLチカできるがarduinoとPCがつながっててちょっとアレだったので線を引っこ抜いて外部電源に切り替え

7、nexus7から「a」を送ったり「b」を送ったりして遊ぶ。なんとなく感度が良くない気がする。

結果↓



arduinoのコード(抜粋) : ほぼarduino IDE に入っているサンプルのまんま、serial部分を足しただけ。

int led = 2;

void setup() {               

  pinMode(led, OUTPUT); 
  Serial1.begin(9600); 
}

void loop() {

  if(Serial1.available()>0){
   
    char data1=Serial1.read();
  if(data1=='a'){
    digitalWrite(led, HIGH);
    Serial1.print("a received led on\n");   // turn the LED on (HIGH is the voltage level)
  delay(1000);   

  else if(data1=='b'){ 
  digitalWrite(led, LOW);  
  Serial1.print("b received led off\n");
  delay(1000);    
  }else
{
    Serial1.read();
  }

}
Serial1.println("serial from arduino");
delay(2000);
}

今後の展開(2日後予定) : android側で出来合いのソフトじゃなくてFTDriverライブラリを使ってラジコンとか操縦できるといいね(android傾けたりして)

おわり





2013年3月30日土曜日

さしあたってwindowsでc++でgui(wxwidets)を作るために必要な情報まとめ(作成中)

参考リンク1(mingwへのwxwidetsのコンパイル、インストールの仕方)
ここを参考にしないと今のところwxwidetsがコンパイルできずに詰む 
http://mynote.mydns.jp/blog/wordpress/?tag=wxwidgets