/* FaBoDeviceService.java Copyright (c) 2014 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.fabo; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.widget.Toast; import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialProber; import com.hoho.android.usbserial.util.SerialInputOutputManager; import org.deviceconnect.android.deviceplugin.fabo.param.ArduinoUno; import org.deviceconnect.android.deviceplugin.fabo.param.FaBoConst; import org.deviceconnect.android.deviceplugin.fabo.param.FirmataV32; import org.deviceconnect.android.deviceplugin.fabo.profile.FaBoGPIOProfile; import org.deviceconnect.android.deviceplugin.fabo.profile.FaBoSystemProfile; import org.deviceconnect.android.deviceplugin.fabo.service.FaBoService; import org.deviceconnect.android.event.Event; import org.deviceconnect.android.event.EventManager; import org.deviceconnect.android.event.cache.MemoryCacheController; import org.deviceconnect.android.message.DConnectMessageService; import org.deviceconnect.android.profile.SystemProfile; import org.deviceconnect.android.service.DConnectService; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; /** * 本デバイスプラグインのプロファイルをDeviceConnectに登録するサービス. * @author NTT DOCOMO, INC. */ public class FaBoDeviceService extends DConnectMessageService { /** ServiceId. */ private static final String SERVICE_ID = "gpio_service_id"; /** Tag. */ private final static String TAG = "FABO_PLUGIN_SERVICE"; /** ロガー. */ private final Logger mLogger = Logger.getLogger("fabo.dplugin"); /** USB Port. */ private static UsbSerialPort mSerialPort = null; /** USBのSerial IO Manager. */ private static SerialInputOutputManager mSerialIoManager; /** Executor. */ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); /** Port status. */ private static int digitalPortStatus[] = {0,0,0}; /** Analog pin values. A0-A5(Arduino UNO). */ private static int analogPinValues[] = new int[20]; /** ServiceID. */ private static String mServiceId = ""; /** Thredd. */ private static Thread mThread; /** PinStatus. */ private static int pinMode[] = new int[20]; /** PinPort. */ private static int pinPort[] = {ArduinoUno.PORT_D0, ArduinoUno.PORT_D1, ArduinoUno.PORT_D2, ArduinoUno.PORT_D3, ArduinoUno.PORT_D4, ArduinoUno.PORT_D5, ArduinoUno.PORT_D6, ArduinoUno.PORT_D7, ArduinoUno.PORT_D8, ArduinoUno.PORT_D9, ArduinoUno.PORT_D10, ArduinoUno.PORT_D11, ArduinoUno.PORT_D12, ArduinoUno.PORT_D13}; /** PinPort. */ private static int pinBit[] = {ArduinoUno.BIT_D0, ArduinoUno.BIT_D1, ArduinoUno.BIT_D2, ArduinoUno.BIT_D3, ArduinoUno.BIT_D4, ArduinoUno.BIT_D5, ArduinoUno.BIT_D6, ArduinoUno.BIT_D7, ArduinoUno.BIT_D8, ArduinoUno.BIT_D9, ArduinoUno.BIT_D10, ArduinoUno.BIT_D11, ArduinoUno.BIT_D12, ArduinoUno.BIT_D13}; /** ServiceIDを保持する. */ private List<String> mServiceIdStore = new ArrayList<String>(); /** Statusを保持. */ private static int mStatus; @Override public void onCreate() { super.onCreate(); // Set status. setStatus(FaBoConst.STATUS_FABO_NOCONNECT); // Eventの設定. EventManager.INSTANCE.setController(new MemoryCacheController()); // pinモードの初期状態を保存. for(int i = 0; i < 14; i++){ pinMode[i] = FirmataV32.PIN_MODE_GPIO_OUT; } for(int i = 14; i < 20; i++){ pinMode[i] = FirmataV32.PIN_MODE_ANALOG; } // USBのEvent用のBroadcast Receiverを設定. IntentFilter mIntentFilter = new IntentFilter(); mIntentFilter.addAction(FaBoConst.DEVICE_TO_ARDUINO_OPEN_USB); mIntentFilter.addAction(FaBoConst.DEVICE_TO_ARDUINO_CHECK_USB); mIntentFilter.addAction(FaBoConst.DEVICE_TO_ARDUINO_CLOSE_USB); mIntentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(mUSBEvent, mIntentFilter); // FaBoサービスを登録. getServiceProvider().addService(new FaBoService()); } @Override protected void onManagerUninstalled() { // Managerアンインストール検知時の処理。 if (BuildConfig.DEBUG) { mLogger.info("Plug-in : onManagerUninstalled"); } } @Override protected void onManagerTerminated() { // Manager正常終了通知受信時の処理。 if (BuildConfig.DEBUG) { mLogger.info("Plug-in : onManagerTerminated"); } } @Override protected void onManagerEventTransmitDisconnected(final String origin) { // ManagerのEvent送信経路切断通知受信時の処理。 if (BuildConfig.DEBUG) { mLogger.info("Plug-in : onManagerEventTransmitDisconnected"); } if (origin != null) { EventManager.INSTANCE.removeEvents(origin); List<Event> events = EventManager.INSTANCE.getEventList(FaBoGPIOProfile.PROFILE_NAME, FaBoGPIOProfile.ATTRIBUTE_ON_CHANGE); for (Event event : events) { if (event.getOrigin().equals(origin)) { String serviceId = event.getServiceId(); Iterator serviceIds = mServiceIdStore.iterator(); while(serviceIds.hasNext()){ String tmpServiceId = (String)serviceIds.next(); if(tmpServiceId.equals(serviceId)) serviceIds.remove(); } } } } else { resetPluginResource(); } } @Override protected void onDevicePluginReset() { // Device Plug-inへのReset要求受信時の処理。 if (BuildConfig.DEBUG) { mLogger.info("Plug-in : onDevicePluginReset"); } resetPluginResource(); } /** * リソースリセット処理. */ private void resetPluginResource() { /** 全イベント削除. */ EventManager.INSTANCE.removeAll(); /** serviceId保持テーブル リセット. */ mServiceIdStore.clear(); } /** * 値監視用のThread. */ private void startWatchFirmata(){ if(mThread == null) { mThread = new Thread(new Runnable() { public void run() { do { for (int s = 0; s < mServiceIdStore.size(); s++) { String serviceId = mServiceIdStore.get(s); List<Event> events = EventManager.INSTANCE.getEventList(serviceId, FaBoGPIOProfile.PROFILE_NAME, null, FaBoGPIOProfile.ATTRIBUTE_ON_CHANGE); synchronized (events) { for (Event event : events) { Bundle pins = new Bundle(); for (int i = 0; i < 20; i++) { if (pinMode[i] == FirmataV32.PIN_MODE_GPIO_IN) { pins.putInt("" + i, getGPIOValue(pinPort[i], pinBit[i])); } else if (pinMode[i] == FirmataV32.PIN_MODE_ANALOG) { pins.putInt("" + i, getAnalogValue(i)); } } // Eventに値をおくる. Intent intent = EventManager.createEventMessage(event); intent.putExtra("pins", pins); sendEvent(intent, event.getAccessToken()); } } } try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } while (true); } } ); mThread.start(); } } /** * Threadを停止. */ private void endWatchFirmata(){ mThread = null; } @Override protected SystemProfile getSystemProfile() { return new FaBoSystemProfile(); } /** * USBをOpenする. */ private void openUsb(){ // USBManagerを取得. UsbManager mUsbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE); // 使用可能なUSB Portを取得. final List<UsbSerialDriver> drivers = UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager); final List<UsbSerialPort> result = new ArrayList<UsbSerialPort>(); if(drivers.size() == 0) { sendResult(FaBoConst.CAN_NOT_FIND_USB); return; } else { // 発見したPortをResultに一時格納. for (final UsbSerialDriver driver : drivers) { final List<UsbSerialPort> ports = driver.getPorts(); result.addAll(ports); } // 一番最後に発見されたPortをmSerialPortに格納. int count = result.size(); mSerialPort = result.get(count - 1); // PortをOpen. UsbDeviceConnection connection = mUsbManager.openDevice(mSerialPort.getDriver().getDevice()); if (connection == null) { sendResult(FaBoConst.FAILED_OPEN_USB); return; } try { // Firmataは、57600bpsで接続する. mSerialPort.open(connection); mSerialPort.setParameters(FirmataV32.BAUDRATE, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); sendResult(FaBoConst.SUCCESS_CONNECT_ARDUINO); } catch (IOException e) { sendResult(FaBoConst.FAILED_CONNECT_ARDUINO); Toast.makeText(this.getContext(), "Error:" + e, Toast.LENGTH_SHORT).show(); try { mSerialPort.close(); } catch (IOException e2) { // Ignore. } mSerialPort = null; return; } } onDeviceStateChange(); } /** * USBをClose. */ private void closeUsb(){ endWatchFirmata(); stopIoManager(); if(mSerialPort != null) { try { mSerialPort.close(); mSerialPort = null; } catch (IOException ignored) {} } setStatus(FaBoConst.STATUS_FABO_NOCONNECT); DConnectService service = getServiceProvider().getService(SERVICE_ID); if (service != null) { service.setOnline(false); } } /** * ステータスを変化する. * @param status ステータス */ private void setStatus(int status){ mStatus = status; Log.i(TAG, "status:" + status); } /** * シリアル通信を開始. */ private void onDeviceStateChange() { // Init. stopIoManager(); startIoManager(); intFirmata(); // Portの状態をすべて0(Low)にする. digitalPortStatus[0] = 0; // 0000 0000 digitalPortStatus[1] = 0; // 0000 0000 digitalPortStatus[2] = 0; // 0000 0000 // 3秒だってFirmataを検出できない場合はエラー. new Handler().postDelayed(checkStatus, 3000); // Statusをinitへ. setStatus(FaBoConst.STATUS_FABO_INIT); // FirmataのVersion取得のコマンドを送付 byte command[] = {(byte)0xF9}; SendMessage(command); } /** * Firmata未検出時のTimeout処理. */ private final Runnable checkStatus = new Runnable() { @Override public void run() { if(mStatus == FaBoConst.STATUS_FABO_INIT){ sendResult(FaBoConst.FAILED_CONNECT_FIRMATA); setStatus(FaBoConst.STATUS_FABO_NOCONNECT); } } }; /** * シリアル通信をストップする. */ private void stopIoManager() { if (mSerialIoManager != null) { mSerialIoManager.stop(); mSerialIoManager = null; } } /** * シリアル通信を開始する. */ private void startIoManager() { if (mSerialPort != null) { mSerialIoManager = new SerialInputOutputManager(mSerialPort, mListener); mExecutor.submit(mSerialIoManager); } } /** * Firmataの初期設定. */ private void intFirmata(){ byte[] command = new byte[2]; // AnalogPin A0-A5の値に変化があったら通知する設定をおこなう(Firmata) for(int analogPin = 0; analogPin < 7; analogPin++) { command[0] = (byte) (FirmataV32.REPORT_ANALOG + analogPin); command[1] = (byte) FirmataV32.ENABLE; SendMessage(command); } // Portのデジタル値に変化があったら通知する設定をおこなう(Firmata) for(int digitalPort = 0; digitalPort < 3; digitalPort++) { command[0] = (byte) (FirmataV32.REPORT_DIGITAL + digitalPort); command[1] = (byte) FirmataV32.ENABLE; SendMessage(command); } } /** * Arduino側から返答のあるメッセージを受信するLisener. */ private final SerialInputOutputManager.Listener mListener = new SerialInputOutputManager.Listener() { @Override public void onRunError(Exception e) { } @Override public void onNewData(final byte[] data) { for(int i = 0; i < data.length; i++) { if (mStatus == FaBoConst.STATUS_FABO_INIT) { if ((i + 2) < data.length) { if ((byte) (data[i] & 0xff) == (byte) 0xf9 && (byte) (data[i + 1] & 0xff) == (byte) 0x02 && (byte) (data[i + 2] & 0xff) == (byte) 0x04) { setStatus(FaBoConst.STATUS_FABO_RUNNING); sendResult(FaBoConst.SUCCESS_CONNECT_FIRMATA); startWatchFirmata(); DConnectService service = getServiceProvider().getService(SERVICE_ID); if (service != null) { service.setOnline(true); } } } } else { // 7bit目が1の場合は、コマンド. if ((data[i] & 0x80) == 0x80) { if ((byte) (data[i] & 0xf0) == FirmataV32.ANALOG_MESSAGE) { if ((i + 2) < data.length) { int pin = (data[i] & 0x0f); if (pin < 7) { int value = ((data[i + 2] & 0xff) << 7) + (data[i + 1] & 0xff); analogPinValues[pin + 14] = value; } } } else if ((byte) (data[i] & 0xf0) == FirmataV32.DIGITAL_MESSAGE) { if ((i + 2) < data.length) { int port = (data[i] & 0x0f); int value = ((data[i + 2] & 0xff) << 8) + (data[i + 1] & 0xff); // Arduino UNOは3Portまで. if (port < 3) { // 1つ前の値を取得する. int lastValue = digitalPortStatus[port]; // 取得した値は保存する. digitalPortStatus[port] = value; } } } } } } } }; /** * Broadcast receiver for usb event. */ private BroadcastReceiver mUSBEvent = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { String action = intent.getAction(); switch (action) { case FaBoConst.DEVICE_TO_ARDUINO_OPEN_USB: openUsb(); break; case FaBoConst.DEVICE_TO_ARDUINO_CHECK_USB: sendStatus(mStatus); break; case FaBoConst.DEVICE_TO_ARDUINO_CLOSE_USB: closeUsb(); setStatus(FaBoConst.STATUS_FABO_NOCONNECT); break; case UsbManager.ACTION_USB_DEVICE_DETACHED: closeUsb(); setStatus(FaBoConst.STATUS_FABO_NOCONNECT); break; } } }; /** * Arduinoへのメッセージの送信 * @param msg String型のメッセージ */ public void SendMessage(String msg) { if (mSerialIoManager != null) { try{ mSerialPort.write(msg.getBytes(), 10000); } catch(Exception e){ onDeviceStateChange(); } } else { setStatus(FaBoConst.STATUS_FABO_NOCONNECT); } } /** * メッセージの送信 * @param mByte Byte型のメッセージ */ public void SendMessage(byte[] mByte) { if(mSerialIoManager != null) { try { mSerialPort.write(mByte, 1000); } catch (Exception e){ onDeviceStateChange(); } } else { setStatus(FaBoConst.STATUS_FABO_NOCONNECT); } } /** * Portの状態の保存. * @param port Port番号 * @param status Portのステータス */ public void setPortStatus(int port, int status){ digitalPortStatus[port] = status; Log.i(TAG, "setStatus:" + digitalPortStatus[port]); } /** * Portの状態の取得. * @param port Port番号 * */ public int getPortStatus(int port){ return digitalPortStatus[port]; } /** * Digitalの値の取得. * @param port ArduinoのPort番号. */ public int getDigitalValue(int port){ return digitalPortStatus[port]; } /** * onChangeイベントの登録. * @param serviceId 現在接続中のデバイスプラグインのServiceId. */ public void registerOnChange(String serviceId) { mServiceIdStore.add(serviceId); for(int i = 0; i < mServiceIdStore.size(); i++) { List<Event> events = EventManager.INSTANCE.getEventList(mServiceId, FaBoGPIOProfile.PROFILE_NAME, null, FaBoGPIOProfile.ATTRIBUTE_ON_CHANGE); } } /** * onChangeイベントの削除. */ public void unregisterOnChange(String serviceId) { Iterator serviceIds = mServiceIdStore.iterator(); while(serviceIds.hasNext()){ String tmpServiceId = (String)serviceIds.next(); if(tmpServiceId.equals(serviceId)) serviceIds.remove(); } for(int i = 0; i < mServiceIdStore.size(); i++) { List<Event> events = EventManager.INSTANCE.getEventList(mServiceId, FaBoGPIOProfile.PROFILE_NAME, null, FaBoGPIOProfile.ATTRIBUTE_ON_CHANGE); } } /** * ANALOGの値の取得. * @param pin PIN番号 * @return Analogの値 */ public int getAnalogValue(int pin) { return analogPinValues[pin]; } /** * GPIOの値の取得. * @param port PORT * @param pin PIN * @return GPIOの値 */ public int getGPIOValue(int port, int pin) { int value = digitalPortStatus[port]; if((value&pin) == pin){ return 1; } else{ return 0; } } /** * Pinの状態を保存する. * @param pinNo pin番号  * @param mode modeの値 */ public void setPin(int pinNo, int mode){ pinMode[pinNo] = mode; } /** * Activityにメッセージを返信する. * @param resultId 結果のID. */ private void sendResult(int resultId){ Intent intent = new Intent(FaBoConst.DEVICE_TO_ARDUINO_OPEN_USB_RESULT); intent.putExtra("resultId", resultId); sendBroadcast(intent); } /** * Activityにステータス状態を返信する. * @param statusId 結果のID. */ private void sendStatus(int statusId){ Intent intent = new Intent(FaBoConst.DEVICE_TO_ARDUINO_CHECK_USB_RESULT); intent.putExtra("statusId", statusId); sendBroadcast(intent); } }