UnityでWi-Fi受信可能な加速度コントローラーを作る

##はじめに
Wiiリモコンのような、振ったり傾けたりしたデータをUnityで利用できると、
VR、エクササイズなど色々なジャンルのコンテンツを作ることができるようになります。
![2016-12-04_131816.jpg](https://qiita-image-store.s3.amazonaws.com/0/59911/a8c0b22f-9d00-e559-746c-c23fd001554a.jpeg)

今回、[がじぇっとるねさす](http://gadget.renesas.com/ja/)に出品した[加速度コントローラーを使ったアプリ](https://youtu.be/_Pqq1fFVGgY)で使用した、Wi-Fiでデータ送信可能な加速度コントローラーの作り方を共有・公開させていただきました。
![DSC02224.png](https://qiita-image-store.s3.amazonaws.com/0/59911/a5fd4020-96d1-e628-fa84-ea2477c6d03d.png)

##揃えるもの
###パーツ
・[Ｗｉ－Ｆｉモジュール　ＥＳＰ－ＷＲＯＯＭ－０２　ＤＩＰ化キット](http://akizukidenshi.com/catalog/g/gK-09758/)
・[GR-COTTON](http://akizukidenshi.com/catalog/g/gK-09867/)
・[３軸加速度センサモジュール　ＫＸＳＣ７－２０５０](http://akizukidenshi.com/catalog/g/gK-07243/)
###工作道具
・[ワイヤーストリッパー](https://www.amazon.co.jp/VESSEL-%E3%83%99%E3%83%83%E3%82%BB%E3%83%AB-No-3500E-2-%E3%83%AF%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%B9%E3%83%88%E3%83%AA%E3%83%83%E3%83%91%E3%83%BC/dp/B000CEAXM4/ref=sr_1_5?ie=UTF8&qid=1481521568&sr=8-5&keywords=%E3%83%AF%E3%82%A4%E3%83%A4%E3%83%BC%E3%82%B9%E3%83%88%E3%83%AA%E3%83%83%E3%83%91%E3%83%BC)（またはニッパー、ナイフ等）
・配線
・ハンダ
・ハンダこて
###電源
・[USBケーブル](https://www.amazon.co.jp/%E3%82%A8%E3%83%AC%E3%82%B3%E3%83%A0-%E5%85%85%E9%9B%BB%E5%B0%82%E7%94%A8-Micro-USB%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB-0-1m-%E3%83%96%E3%83%A9%E3%83%83%E3%82%AF/dp/B00DVT96RK/ref=sr_1_2?s=computers&ie=UTF8&qid=1481520722&sr=1-2&keywords=usb%E3%82%B1%E3%83%BC%E3%83%96%E3%83%AB+%E3%82%A8%E3%83%AC%E3%82%B3%E3%83%A0%E3%80%800.1m)
・[スマホ用バッテリー](https://www.amazon.co.jp/BUFFALO-BSMPB3301P1BK-iPhone7Plus-Nintendo/dp/B01KN3I0R8/ref=dp_ob_title_ce)
###ソフトウェア
・KurumiWriter(後述)
##ハードウェア接続
まずは3つのパーツを下記のように配線・半田付けします。
![DSC02264.jpg](https://qiita-image-store.s3.amazonaws.com/0/59911/0d863ab9-a1dd-6d11-8c57-f27a0616c0f5.jpeg)
Wi-FiモジュールとGR-COTTONの接続に関しての説明は[こちら](http://gadget.renesas.com/ja/product/cotton_sp12.html)、
3軸加速度センサーのデータシートは[こちら](http://akizukidenshi.com/download/ds/akizuki/AE-KXSC7-2050.pdf)にあります。
半田付けしたら完成です。

##ソフトウェア作成
###GR-COTTON用加速度送信プログラム
[こちら](http://gadget.renesas.com/ja/product/cotton_sp1.html)の記事を参考に、WebコンパイラーでGR-COTTON用のプログラムを作成します。

![スクリーンショット 2016-12-12 14.50.24.png](https://qiita-image-store.s3.amazonaws.com/0/59911/333c47a4-37d9-57ec-99b0-8a7fb49689af.png)

準備ができたら、標準プロジェクトテンプレートにあるgr_sketch.cppの内容を下記のように変更します。

```gr_sketch.cpp
/**
 * @example UDPClientMultiple.ino
 * @brief The UDPClientMultiple demo of library WeeESP8266. 
 * @author Wu Pengfei<pengfei.wu@itead.cc> 
 * @date 2015.02
 * 
 * @par Copyright:
 * Copyright (c) 2015 ITEAD Intelligent Systems Co., Ltd. \n\n
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version. \n\n
 * 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.
 */

#include "ESP8266.h"

#define HOST_PORT   (7001)
#define RECV_PORT   (7001)
#define ESP_Serial  Serial1
#define PIN_R  (22)
#define PIN_G  (23)
#define PIN_B  (24)

// A3,A4,A5 are analog xyz
#define pin_x (A3)
#define pin_y (A4)
#define pin_z (A5)
int valSq,valSqOld;
int x,y,z,ax,ay,az,ox,oy,oz;

const char *softSsid = "skipshoes4";
const char *softPwd = "123456789";
static uint8_t mux_id = 0;
int intCnt = 0;
bool isLEDDisp;

ESP8266 wifi;

void updateAccInfo(){
	ox = x;
	oy = y;
	oz = z;
	x = analogRead(pin_x);
	y = analogRead(pin_y);
	z = analogRead(pin_z);
	z = 1023-z; // 基盤の取付向きが逆

	ax = x-ox;
	ay = y-oy;
	az = z-oz;
}

int setAccStringArr(String strarr[],String ipList){
	int cnt = 0;
	if(ipList.length()>2){
		char charBuff[ipList.length()+2];
		ipList.toCharArray(charBuff,ipList.length());
		strarr[cnt]="";
		bool isTrim = false;
		for(int i=0; i<ipList.length();++i){
			int ascii = (int)(charBuff[i]);
			if(ascii>13){
				if(isTrim==false){
					if(charBuff[i]==','){
						isTrim = true;
					}else{
						strarr[cnt]+=String(charBuff[i]);
					}
				}
			}else if((ascii==10)||(ascii==0)){
				cnt++;
				isTrim = false;
				strarr[cnt]="";
			}
		}
	}
	return cnt;
}

void setupWifi(){
	wifi.begin(ESP_Serial);
    
    Serial.print("FW Version:");
    Serial.println(wifi.getVersion().c_str());

    if (wifi.setOprToSoftAP()) {
        Serial.print("to softAp ok\r\n");
    } else {
        Serial.print("to softAp err\r\n");
    }
    delay(100);
    
    if(wifi.setSoftAPParam(softSsid, softPwd, 7, 4)){
        Serial.print("setSoftAPParam4 ok\r\n");
    }else{
        Serial.print("setSoftAPParam4 err\r\n");
    }
    delay(100);
    
    if (wifi.enableMUX()) {
        Serial.print("multi ok\r\n");
    } else {
        Serial.print("multi err\r\n");
    }
}

bool sendSub(String hostName){
    int ret = false;
    uint8_t buffer[128] = {0};
    mux_id++;
    if (mux_id >= 5) {
        mux_id = 0;
    }
    if (wifi.registerUDP(mux_id, hostName, HOST_PORT)) {
        Serial.println("reg udp ok");
    } else {
        Serial.println("reg udp err");
    }
    
    String sendStr = "x:" + String(x)+",y:" + String(y)+",z:" + String(z);
    
    wifi.send(mux_id,(const uint8_t*)(sendStr.c_str()), strlen(sendStr.c_str()));
    
    if (wifi.unregisterUDP(mux_id)) {
    	ret = true;
        Serial.println("unreg udp ok");
    } else {
        Serial.println("unreg udp err");
    }
    return ret;
}

bool sendSubAll(){
	String ipList = wifi.getJoinedDeviceIP();
	String StrArr[10];
	int cnt = setAccStringArr(StrArr,ipList);
	if(cnt<=0){
	    Serial.println("List=null");
	    return false;
	}
	Serial.println("List=");
	for(int i=0; i<cnt;++i){
	    Serial.println(StrArr[i]);
		sendSub(StrArr[i]);
	}
	return true;
}

void setLED(bool r, bool g, bool b){
	digitalWrite(PIN_R, r ? LOW : HIGH);
	digitalWrite(PIN_G, g ? LOW : HIGH);
	digitalWrite(PIN_B, b ? LOW : HIGH);
}

void setup(void)
{
	pinMode(PIN_R, OUTPUT);
	pinMode(PIN_G, OUTPUT);
	pinMode(PIN_B, OUTPUT);
    pinMode(pin_x, INPUT);
    pinMode(pin_y, INPUT);
    pinMode(pin_z, INPUT);
    x=0,y=0,z=0,ax=0,ay=0,az=0,ox=0,oy=0,oz=0;
    isLEDDisp = false;

    Serial.begin(9600);
	setupWifi();
    updateAccInfo();
    Serial.print("setup end\r\n");
}

void loop(void)
{
    uint8_t buffer[256] = {0};
    uint32_t len;
    updateAccInfo();
	sendSubAll();

	isLEDDisp = !isLEDDisp;
	if(!isLEDDisp){
		setLED(false,false,false);
	}else{
		setLED(true,false,false);
	}
}
```
コンパイルが成功し、さらに書き込みが終了するとGR-COTTONのLEDが赤く光ります。

###Unity用加速度受信スクリプト
続いてUnity側で受信スクリプトを作成します。

空のGameObjectを作り、下記スクリプトをアタッチします。
![スクリーンショット 2016-12-12 13.59.05.png](https://qiita-image-store.s3.amazonaws.com/0/59911/437ed737-412b-1314-fd3e-3e3251fe7c01.png)

```c#:UDPReceiveTest.cs
using UnityEngine;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class UDPReceiveTest : MonoBehaviour
{
	static readonly int RingSize = 16;
    public int port = 7001;
	public GUISkin skin;
	public bool isTest = true;
	private string portStr;
    private UdpClient udp;
	private Thread thread;
	private string[] recvTextArr;
	private int buffPtr;
    private bool isReceiving;

	public string[] RecvTextArr{ get { return recvTextArr; } }
	public int BuffPtr{ get { return buffPtr; } }

	private void buffClear(){
		recvTextArr = new string[RingSize];
		buffPtr = 0;
		for(int i = 0; i < recvTextArr.Length; ++i){
			recvTextArr[i] = "";
		}
	}

    void Start()
    {
        isReceiving = true;
		portStr = port.ToString ();
		buffClear ();
        udp = null;
        thread = null;
        udpStart();
    }

    void Update()
    {
		if (isReceiving) {
			udpStart ();
		} else {
			udpStop ();
		}
    }

    void OnApplicationQuit()
    {
        udpStop();
    }

    void OnGUI()
    {
		if (!isTest) {
			return;
		}
		GUI.skin = skin;
        GUILayout.BeginArea(new Rect(350, 40, 550, 550));
		GUILayout.BeginHorizontal();
		portStr = GUILayout.TextArea (portStr);
		if (GUILayout.Button(isReceiving ? "Stop" : "Start"))
		{
			isReceiving = !isReceiving;
			if (isReceiving) {
				int resPort = 0;
				if (int.TryParse (portStr, out resPort)) {
					port = resPort;
					udpStart();
				} else {
					portStr = port.ToString ();
				}
			}
		}
		if (GUILayout.Button("Clear"))
		{
			buffClear ();
		}
		GUILayout.EndHorizontal();
		int ptr = buffPtr;
		string outStr = "";
		for(int i = 0; i < recvTextArr.Length; ++i){
			outStr += ((i>0)?"\n":"")+recvTextArr [ptr];
			ptr--;
			if (ptr < 0) {
				ptr += RingSize;
			}
		}
		GUILayout.TextArea(outStr);
        GUILayout.EndArea();
    }

    private void udpStart()
    {
        if (udp == null)
        {
            udp = new UdpClient(port);
            udp.Client.ReceiveTimeout = 1000;
        }
        if (thread == null)
        {
            thread = new Thread(new ThreadStart(ThreadMethod));
            thread.Start();
        }
    }
    private void udpStop()
    {
        if (thread != null)
        {
            thread.Abort();
            thread = null;
        }
        if (udp != null)
        {
            udp.Close();
            udp = null;
        }
    }

    private void ThreadMethod()
    {
        Debug.Log("RecvStt");
        while (true)
        {
            if (isReceiving)
            {
                try
                {
                    IPEndPoint remoteEP = null;
                    byte[] data = udp.Receive(ref remoteEP);
					buffPtr = (buffPtr+1) % RingSize;
					recvTextArr[buffPtr] = Encoding.ASCII.GetString(data);
//					Debug.Log("Recv:"+recvTextArr[buffPtr]);
                }
                catch (SocketException e)
                {
//                    Debug.Log(e.ToString());
                }
            }
            else
            {
            }
        }
    }
}
```

##受信
ハードウェア（GR-COTTON）の電源が入った状態で、PCのWi-Fiを
上記GR-COTTON用プログラム中で設定した

```
const char *softSsid = "skipshoes4";
const char *softPwd = "01234567";
```
に接続します。

![スクリーンショット-2016-12-12-13.53.17.jpg](https://qiita-image-store.s3.amazonaws.com/0/59911/6349d724-c6ae-4f61-8a17-7077b0215d20.jpeg)

[追記]ネットワーク設定で **プライベートネットワーク** を指定します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/23bc5365-eb29-2fdc-038b-5af5b2823d4e.png)

macならIPv4の設定を手動にしてIPアドレスを192.168.4.2にセット
![スクリーンショット 2023-12-03 12.21.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/c48b5d51-74e7-c930-0874-721fd8ba1708.png)


接続できたらUnityを再生すると加速度データ（文字列）が流れてきます。
![スクリーンショット 2016-12-12 15.15.58.png](https://qiita-image-store.s3.amazonaws.com/0/59911/65a3877b-c24b-a42b-1f5d-71020b5b67df.png)

あとはデータを利用して、[好きなアプリ](https://youtu.be/rr7qgTm7V7o)を作ることができます。

*モバイルの場合、端末でUnityの起動を確認してからWiFiを接続しないとUnityが起動しないようです。*

[WIO-NODE](http://www.marutsu.co.jp/pc/i/607381/)というボードを使用して同様のコントローラーを作るには[こちら](http://qiita.com/ELIXIR/items/76410255626983197e6a)。