ウェブと通信するためにまずウェブサーバが必要で、そしてパソコンとマイコンと通信するためにシリアル通信というのは必要です。
今回の工作はAndroidにウェブサーバとシリアル通信変換という二つのプログラミングを一つのアプリにまとめて作ってみました。
今回の工作に使われているものは次となる。
1. MK808B ミニPC (Android 4.1 が搭載されています)
2. HDMI端子つきのモニター
3. USB - シリアル変換 (FT232RL)
4. マイコン (AVR Atmega 328p)
5. Java およびAndroidの開発環境が整えたパソコン
MK808B
MK808Bは中国製のミニPCです。正直最初はそれにUbuntuを入れてウェブサーバとして使いたかったですが、いろいろやってみたら失敗に終えました。
しかし、MK808BにAndroid 4.1が搭載されていますから、試しにAndroidをウェブサーバとして作ってみたら成功しました。なので、このブログを借りてその経験をみんなとシェアしたいと思います。
この工作はAndroid OSに成功しましたから、たぶんAndroid4.xが搭載されているスマホでも作れると思います。ただし、シリアル通信のところでは、USB OTGが必要なので、それを装備しているスマホでなければいけないと思います。
HTTPウェブサーバを成作
nanoHttpdというライブラリを使いました。まず、Androidの開発環境を開いて新しいAndroidプロジェクトを立ち上げてください。それを”HttpSerialServer”と名付けてください。そして、次のリンクからzipファイルを落としてください。
https://github.com/NanoHttpd/nanohttpd
落としたら、解凍して、ファイルを開いてください。次の場所へ、core->src->main->java->fi->iki->elonen 、NanoHTTPD.javaというファイルがあります。それをコピーし、アンドロイドのプロジェクトのsrcというフォルダに貼り付けてください。
これで、ライブラリのインストールは完了しました。
そして、新しいファイルを新規して、次のサンプルコードを書いて確かめましょう。
まず、新規したファイルをSimpleServer.javaと名づけてに次のコードをコピーしてください。
public class SimpleServer extends NanoHTTPD{
public SimpleServer(String host,int port) {
super(host,port);
}
@Override
public Response serve(String uri, Method method, Map<string,string> header, Map<string,string> parms, Map<string,string> files) {
StringBuilder sb = new StringBuilder();
sb.append("HELLO, this is testing code and you are success if you this messasge");
return new Response(sb.toString());
}
}
そして、MainActivity.javaを開いて、onCreateメソッドのところに次にコードを加えてください。
server = new SimpleServer(SimpleServer.url,SimpleServer.port);
try {
server.start();
System.out.println(getLocalIpAddress(true));
} catch (IOException e) {
e.printStackTrace();
}
onDestroyメソッドに次のソースコードを加えてserverを停止する。
if(server != null){
server.stop();
server = null;
}
MainActivity.javaの全体はこういう感じです。
public class MainActivity extends Activity {
Button smallButton[][];
int m_row = 5,m_col = 5;
SimpleServer server = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
server = new SimpleServer("0.0.0.0",8080);
try {
server.start();
System.out.println(getLocalIpAddress(true));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy(){
super.onDestroy();
if(server != null){
server.stop();
}
}
}
もっとも手取り早い確かめる方法としては、Android携帯にアプリを起動して、PCのブラウザからアクセスします。その際、使うURLは
http://<android ip address>:8080
エミュレータを使う方法もありますが、この記事ではそれに関しては説明しません。
もし、ここまで成功しましたら、ウェブサーバを本格化しましょう。nanohttpdファイルを開いて、webserver->src->main->java->fi->iki->elonenに入ってもらって、そうすると、SimpleWebServer.javaというファイルが見えます。そのファイルはSimpleServer.javaの代わりに使うので、SimpleServer.javaを削除して、SimpleWebServer.javaをコピーして貼り付けましょう。
そして、SimpleWebServerを開いて、いくつ変更点があります。
public static void main(String[] args) … というメソッドを消してください。そして、次のグローバル変数とメソッドを加えてください。
public static String mRecv = "";
public static String mWrite = "";
public static final String path = "/data/user";
public static final String PARMS1 = "send";
これを加えることによってウェブからどういうようなデータを受け取ったのかを確認できます。
また、public Response serve(String uri, Method method… というメソッドを次のように変更してください。
boolean jsonApp = false;
if (!quiet) {
System.out.println(method + " '" + uri + "' ");
Iterator<String> e = header.keySet().iterator();
while (e.hasNext()) {
String value = e.next();
System.out.println(" HDR: '" + value + "' = '" + header.get(value) + "'");
if(value.equals("x-requested-with")){
if(header.get(value).equals("XMLHttpRequest")){
jsonApp = true;
System.out.println("JSON is received");
}
}
}
e = parms.keySet().iterator();
while (e.hasNext()) {
String value = e.next();
System.out.println(" PRM: '" + value + "' = '" + parms.get(value) + "'");
}
e = files.keySet().iterator();
while (e.hasNext()) {
String value = e.next();
System.out.println(" UPLOADED: '" + value + "' = '" + files.get(value) + "'");
}
System.out.println("RECV : "+parms.get(PARMS1));
}
if(jsonApp){
mRecv = parms.get(PARMS1);
JSONObject jObj = new JSONObject();
try {
jObj.put("get", parms.get(PARMS1));
} catch (JSONException e) {
System.err.println(e);
}
return new Response(jObj.toString());
}else{
return serveFile(uri, header, getRootDir());
}
これに変更することによって、ブラウザから受け取ったデータをそのままブラウザに返します。
そして、MainActivity.javaの方はもともとSimpleServerと書いたクラスをすべてSimpleWebServerに変更してください。また、SimpleServerのコンストラクタのパラメータは2個だったが、SimpleWebServerクラスの場合はパラメータ4つがあって、それも次のように変更しなければなりません。
File wwwroot = new File(SimpleServer.path).getAbsoluteFile();
SimpleServer server = new SimpleServer(SimpleServer.url,SimpleServer.port,wwwroot,false);
try {
server.start();
updateText("IPaddr : "+getLocalIpAddress(true)+":"+SimpleServer.port);
updateText("Server configure success");
} catch (IOException ioe) {
updateText("Failed to start server : "+ioe.toString());
}
ここで、/data/userというのはwwwrootファイルの場所です。最後にAndroidManifest.xmlを開いて、次のコードを<uses-sdk>タグの前においてくだいさい。
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
これから、Androidの/data/userところにindex.htmlファイルを加えましょう。そうすると、ブラウザからhtmlファイルをリクエストされたらindex.htmlが返せます。まず、index.htmlを成作しましょう。
次のコードをコピーしてください。
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script type="text/javascript">
$(function(){
var testmsg = $("#test-text");
var recvmsg = $("#recv");
var b = $("#a");
// change "a" to the character you want to send
b.mousedown(function(){
ajaxSend("a");
});
function ajaxSend(snd){
$.ajax({
type: "POST",
url: ".",
dataType: "json",
data: {
send: snd
},
success: function(e){
var jObj;
if(typeof e == "string"){
jObj = JSON.stringify(e);
}else if(typeof e == "object"){
jObj = e;
}
recvmsg.text("a");
},
error: function (XMLHttpRequest, textStatus, errorThrown){
recvmsg.text("error");
}
});
}
});
</script>
</head>
<body>
<p>received : <span id="recv">initial</span></p>
<p id="test-text">test</p>
<input style="height:200px;width:200px;font-size:30px" type="button" id="a" value="A"></input>
</body>
</html>
このコードはまずブラウザにボタンがあって、そのボタンを押すと”a”という文字が送信されます。もしほかの文字にしたい場合はajaxSend関数のパラメータを他の文字に変更すればよいです。
index.htmlをできたらこれをAndroidに保存します。まず、Androidをdebugモードに変更して、PCからadb terminal を開いて、次のコマンドを入力してください。
adb push C:\Users\Username\Documents\index.html /data/user
これでOKです。もしindex.htmlが入っているかを確認したい場合は、次のコマンドを使ってください。
adb shell
cd /data/user
ls -l
Httpサーバはこれで一端終了です。
USB-シリアル変換を実装
まず、自分のデバイスはちゃんとUSB OTGとして機能できることを確認してください。
ネットで検索してみると、偶然発見しました、FTDI社はAndroid向けのシリアルとUSB変換ライブラリが用意しているということ。よって、せっかくFTDI社がライブラリを用意したので、使いましょうということで、次のリンクへて、zipファイルを落としてください。
https://github.com/ksksue/FTDriver
ファイルを落としたら解凍して再びAndroidの開発環境に戻りましょう。私の開発環境はeclipseなので、それを使って説明させてもらいます。
まず、FTDriverプロジェクトをAndroidプロジェクトとしてimportします。File->New->Othersをクリックすると、次の画面が表示する。
そして、Android Project from Existing Code を選択して、Nextを押してください。
次に、いま解凍したFTDriverを選択してOKをクリックしてください。そして、FTDriverプロジェクトをチェック付けてFinishをクリックしてください。
FTDriverプロジェクトをimportしたらPackage ExplorerにあるFTDriverを右クリックしてプロパティーをクリックしてください。そして、次のような画面が表示します。
Androidセッションを選択して、LibraryのセッションにIs Library がチェックされているものを確認してください。もしチェックされていなければそれをチェック付けてください。
そして、同様にPackage Explorer からHttpSerialServerを右クリックしてプロパティーを開いてください。
LibraryセッションのところにAddボタンを押してFTPDriverを追加してください。あと、Is Libraryのチェックは必ず外してください。
では、AndroidはUSB OTGとして機能しているかとうかを確認しましょう。MainActivity.javaを開いて、次のグロバール変数を定義してください。
String serialRecv = "";
final int SERIAL_BAUDRATE = FTDriver.BAUD9600;
final int mOutputType = 0;
final boolean SHOW_LOGCAT = false;
FTDriver mSerial;
private boolean mStop=false;
Handler mHandler = new Handler();
そして、次のメソッドを加えてください。
/**
* configure USB, if mSerial is not null, it will return true too.
* @return return true if success else fail
*/
boolean configureUSB(){
if(mSerial == null){
// get service
mSerial = new FTDriver((UsbManager)getSystemService(Context.USB_SERVICE));
mStop=false;
// listen for new devices
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, filter);
if(mSerial.begin(SERIAL_BAUDRATE)) {
mainloop();
return true;
}else{
return false;
}
}else{
return true;
}
}
void stopUSB(){
mSerial.end();
mSerial = null;
mStop=true;
unregisterReceiver(mUsbReceiver);
}
private void mainloop() {
new Thread(mLoop).start();
}
private Runnable mLoop = new Runnable() {
@Override
public void run() {
int i;
int len;
byte[] rbuf = new byte[4096];
for(;;){//this is the main loop for transferring
//////////////////////////////////////////////////////////
// Read and Display to Terminal
//////////////////////////////////////////////////////////
len = mSerial.read(rbuf);
// TODO: UI:Show last line
if(len > 0) {
serialRecv = new String(rbuf);
mHandler.post(new Runnable() {
public void run() {
Log.i(TAG,"Serial Recv : "+serialRecv);
}
});
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(mStop) {
break;
}
}
}
};
// BroadcastReceiver when insert/remove the device USB plug into/from a USB port
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
mSerial.usbAttached(intent);
mSerial.begin(SERIAL_BAUDRATE);
mStop=false;
mainloop();
} else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
mSerial.usbDetached(intent);
mSerial.end();
mStop=true;
}
}
};
そして、onCreateメソッドに次のコードを加えてください。
if(configureUSB()){
System.out.println("Success");
}else{
System.out.println("Failed");
}
そして、onDestroyメソッドに次のコードを加えてください。
if(mSerial != null){
stopUSB();
}
その次に、resのフォルダにxmlというフォルダを追加してください。そして、device_filter.xmlというファイルを新規して、xmlフォルダにいれてください。device_filter.xmlは次のコードを加えてください。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1027" product-id="24577" />
<usb-device vendor-id="1118" product-id="688" />
<usb-device vendor-id="1027" product-id="24592" />
<usb-device vendor-id="1027" product-id="24596" />
</resources>
最後に、AndroidManifest.xmlを開いて次のコードを<activity>の中に定義してください。
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
AndroidManifest.xmlの全体はこんな感じです。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.httpserialserver"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk
android:minSdkVersion="12"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.httpserialserver.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
</application>
</manifest>
これで、コンパイルして、アプリをデバイスにインストールして。そして、FTDRのデバイスに繋いでみてください。もし、USBは挿した瞬間に画面に何か表示したら、つまり成功にデバイスが認識されました。もしそうでなければ、理由は二つしかありません。デバイスはUSB OTGではないことと何か編集すべきなことをを忘れました。
最後の仕上げ
サーバから受け取ったデータをそのままUSBに渡せるようにmLoopインスタンスを次のように書き換えてください。
private Runnable mLoop = new Runnable() {
@Override
public void run() {
updateText("Serial Connection is start");
int i;
int len;
byte[] rbuf = new byte[4096];
String recvFromBrowser;
for(;;){//this is the main loop for transferring
// get the data from browser and send it to MCU
recvFromBrowser = server.getReceviedFromBrowser();
if(recvFromBrowser.length() > 0){
mSerial.write(recvFromBrowser.getBytes(), recvFromBrowser.length());
}
// read the received data from serial buffer
len = mSerial.read(rbuf);
if(len > 0) {
serialRecv = new String(rbuf);
mHandler.post(new Runnable() {
public void run() {
Log.i(TAG,"Serial Recv : "+serialRecv);
}
});
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(mStop) {
break;
}
}
}
};
これで、ウェブからデータを受け取って、そのままUSBに渡せるようになりました。
では、アプリを起動して、ブラウザからウェブサーバへアクセスして、ボタンを押してみてください。マイコン側では”a”という文字が受け取るのはずです。
では、お疲れ様でした!