Управление тележкой из android-a по BT

Пока неспешные китайские и российские почтальоны несут мне механику для будущей гусеничной тележки, развлекаюсь тем, что пишу «терминальный» доступ к своему будущему роботу и наконец то приобщился к программированию для мобильных устройств. Для меня, человека никогда, ни на чем, кроме С, переход к ООП был не прост. Дабы не повторять собственный путь, когда мне понадобится это снова, буду выкладывать свое «творчество» здесь. Может еще кому поможет.

Особые благодарности доке Android SDK с ее примером BluetoothChat и неизвестному парню.
Скачку и установку Android SDK описывать не буду. Весит как чугунный мост и без бубна не заработает. Но, бубен у каждого свой, инет пестрит вариантами, каждый может выбрать на свой вкус. У мена заработал вариант с прямым прописывание пути (set java_exe=«C:\Windows\system32\java.exe») к java.exe в android.bat.
А дальше, чисто код. Код «прямой», без фантазии, как у чайника. Комментировать не стал, вроде и так все понятно, но, будут вопросы, отвечу, разжую. В принципе, эта программа может управлять и не тележкой. Изменить расположение кнопок в layout button_tab3, изменить надписи на кнопках и без изменения кода, можно управлять диммером, нагревателем или чем угодно.

Главная Activity и layout:




<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#000000" >

    <Button
        android:id="@+id/btButton"
        android:layout_width="400px"
        android:layout_height="100px"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="УПРАВЛЕНИЕ КНОПКАМИ"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />

    <Button
        android:id="@+id/btTouch"
        android:layout_width="400px"
        android:layout_height="100px"
        android:layout_above="@+id/btButton"
        android:layout_alignLeft="@+id/btButton"
        android:text="УПРАВЛЕНИЕ ПАЛЬЦЕМ"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />

    <Button
        android:id="@+id/btAcsel"
        android:layout_width="400px"
        android:layout_height="100px"
        android:layout_above="@+id/btTouch"
        android:layout_alignLeft="@+id/btTouch"
        android:text="УПРАВЛЕНИЕ НАКЛОНОМ"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />
<!--
    <Button
        android:id="@+id/btBTConf"
        android:layout_width="400px"
        android:layout_height="100px"
        android:layout_alignLeft="@+id/btButton"
        android:layout_below="@+id/btButton"
        android:layout_marginTop="100px"
        android:text="НАСТРОЙКА BLUETOOTH"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />
-->

    <Button
        android:id="@+id/btConf"
        android:layout_width="400px"
        android:layout_height="100px"
        android:layout_alignLeft="@+id/btButton"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="50px"
        android:text="НАСТРОЙКИ"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />

</RelativeLayout>

Главное Activity, «Main Window». Первым появляется при запуске программы и в зависимости от желания пользователя вызывает остальные Activity.


package com.example.btrobotfull;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener{
	
	private static final int REQUEST_ENABLE_BT = 1;
	
	private BluetoothAdapter mBluetoothAdapter;
	
	Button btnButton;
	Button btnTouch;
	Button btnAcsel;
	//Button btnBTConf;
	Button btnConf;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main_tab3);

Подключаем кнопки и назначаем им обработчик нажатий. В качестве обработчика выступает Activity (public class MainActivity extends Activity implements OnClickListener).


		btnButton = (Button) findViewById(R.id.btButton);
		btnTouch = (Button) findViewById(R.id.btTouch);
		btnAcsel = (Button) findViewById(R.id.btAcsel);
		//btnBTConf = (Button) findViewById(R.id.btBTConf);
		btnConf = (Button) findViewById(R.id.btConf);
		
		btnButton.setOnClickListener(this);
		btnTouch.setOnClickListener(this);
		btnAcsel.setOnClickListener(this);
		//btnBTConf.setOnClickListener(this);
		btnConf.setOnClickListener(this);

Дальше, первым делом определяем, есть ли у нас вообще адаптер Bluetooth, если нет, закрываемся, если есть, но не включен, выводим стандартный диалог с запросом пользователю с просьбой включить BT.


		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		if (mBluetoothAdapter == null) {
			errorExit("Bluetooth ERROR", "Can`t find bluetooth adapter");
		}
		if (!mBluetoothAdapter.isEnabled()) {
		    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
		    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
		}
	}

Дошли до обработчика нажатий кнопок. Каждая кнопка вызывает новый Activity. Таким образом, получается «многооконное» приложение. С помощью кнопок вызываются окно настроек, управления кнопками и тд.



	@Override
	public void onClick(View v) {

		switch (v.getId()) {
	    case R.id.btButton:
	    	Intent intent_button = new Intent(this, ButtonActivity.class);
	    	startActivity(intent_button);
	    	break;
	    //case R.id.btTouch:
	    //	Intent intent_touch = new Intent(this, TouchActivity.class);
	    //	startActivity(intent_touch);
	    //	break;
	    //case R.id.btBTConf:
	    //	Intent intent_bt = new Intent(this, BTActivity.class);
	    //	startActivity(intent_bt);
	    //	break;
	    case R.id.btConf:
	    	Intent intent_pr = new Intent(this, PreferencesActivity.class);
	    	startActivity(intent_pr);
	    	break;
	    default:
	    	break;
		}
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	private void errorExit(String title, String message)
	{
	    Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show();
	    finish();
	}

}


Управление движением и скоростью кнопками.





<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:background="#000000">

    <Button
        android:id="@+id/btCenter"
        android:layout_width="100px"
        android:layout_height="100px"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="250px"
        android:layout_marginTop="200px"
        android:text="C"
        android:visibility="invisible" />

    <Button
        android:id="@+id/btUp"
        android:layout_width="150px"
        android:layout_height="150px"
        android:layout_alignBottom="@+id/btCenter"
        android:layout_alignLeft="@+id/btCenter"
        android:layout_marginBottom="100px"
        android:textColor="#FFFFFF"
        android:text="ВПЕРЕД" />

    <Button
        android:id="@+id/btRight"
        android:layout_width="150px"
        android:layout_height="150px"
        android:layout_alignTop="@+id/btCenter"
        android:layout_toRightOf="@+id/btCenter"
        android:textColor="#FFFFFF"
        android:text="ПРАВО" />

    <Button
        android:id="@+id/btDown"
        android:layout_width="150px"
        android:layout_height="150px"
        android:layout_below="@+id/btCenter"
        android:layout_toLeftOf="@+id/btRight"
        android:textColor="#FFFFFF"
        android:text="НАЗАД" />

    <Button
        android:id="@+id/bt100Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="50px"
        android:layout_marginRight="50px"
        android:textColor="#FFFFFF"
        android:text="100%" />

    <Button
        android:id="@+id/bt90Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_above="@+id/bt100Speed"
        android:layout_alignLeft="@+id/bt100Speed"
        android:textColor="#FFFFFF"
        android:text="90%" />

    <Button
        android:id="@+id/bt80Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_above="@+id/bt90Speed"
        android:layout_alignLeft="@+id/bt90Speed"
        android:textColor="#FFFFFF"
        android:text="80%" />

    <Button
        android:id="@+id/bt30Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_alignLeft="@+id/bt40Speed"
        android:layout_below="@+id/bt40Speed"
        android:textColor="#FFFFFF"
        android:text="30%" />

    <Button
        android:id="@+id/bt70Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_above="@+id/bt80Speed"
        android:layout_alignLeft="@+id/bt80Speed"
        android:textColor="#FFFFFF"
        android:text="70%" />

    <Button
        android:id="@+id/bt60Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_above="@+id/bt80Speed"
        android:layout_toLeftOf="@+id/bt80Speed"
        android:textColor="#FFFFFF"
        android:text="60%" />

    <Button
        android:id="@+id/bt50Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_alignBaseline="@+id/bt60Speed"
        android:layout_alignBottom="@+id/bt60Speed"
        android:layout_toLeftOf="@+id/bt60Speed"
        android:textColor="#FFFFFF"
        android:text="50%" />

    <Button
        android:id="@+id/bt40Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_above="@+id/bt80Speed"
        android:layout_toLeftOf="@+id/bt50Speed"
        android:textColor="#FFFFFF"
        android:text="40%" />

    <Button
        android:id="@+id/bt20Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_below="@+id/bt30Speed"
        android:layout_toLeftOf="@+id/bt50Speed"
        android:textColor="#FFFFFF"
        android:text="20%" />

    <Button
        android:id="@+id/bt10Speed"
        android:layout_width="120px"
        android:layout_height="100px"
        android:layout_below="@+id/bt20Speed"
        android:layout_toLeftOf="@+id/bt50Speed"
        android:textColor="#FFFFFF"
        android:text="10%" />

    <Button
        android:id="@+id/btStop"
        android:layout_width="240px"
        android:layout_height="100px"
        android:layout_below="@+id/bt90Speed"
        android:layout_toLeftOf="@+id/bt100Speed"
        android:textColor="#FFFFFF"
        android:text="СТОП" />

    <Button
        android:id="@+id/btLeft"
        android:layout_width="150px"
        android:layout_height="150px"
        android:layout_above="@+id/btDown"
        android:layout_toLeftOf="@+id/btUp"
        android:textColor="#FFFFFF"
        android:text="ЛЕВО" />


</RelativeLayout>

Окно управления тележкой кнопками.


package com.example.btrobotfull;

import java.lang.ref.WeakReference;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class ButtonActivity extends Activity implements OnClickListener{
	
	private cBluetooth bl;
	
	//private String address = "00:01:95:0C:68:E7";
	private String address = "";
	private boolean BT_is_connect;
	
	Button btnStop;
	Button btn10Speed;
	Button btn20Speed;
	Button btn30Speed;
	Button btn40Speed;
	Button btn50Speed;
	Button btn60Speed;
	Button btn70Speed;
	Button btn80Speed;
	Button btn90Speed;
	Button btn100Speed;
	Button btnUp;
	Button btnDown;
	Button btnLeft;
	Button btnRight;

	SharedPreferences sPref;
	final String SAVED_MAC = "00:00:00:00:00:00";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.button_tab3);
		
		btnStop = (Button) findViewById(R.id.btStop);
		btn10Speed = (Button) findViewById(R.id.bt10Speed);
		btn20Speed = (Button) findViewById(R.id.bt20Speed);
		btn30Speed = (Button) findViewById(R.id.bt30Speed);
		btn40Speed = (Button) findViewById(R.id.bt40Speed);
		btn50Speed = (Button) findViewById(R.id.bt50Speed);
		btn60Speed = (Button) findViewById(R.id.bt60Speed);
		btn70Speed = (Button) findViewById(R.id.bt70Speed);
		btn80Speed = (Button) findViewById(R.id.bt80Speed);
		btn90Speed = (Button) findViewById(R.id.bt90Speed);
		btn100Speed = (Button) findViewById(R.id.bt100Speed);
		btnUp = (Button) findViewById(R.id.btUp);
		btnDown = (Button) findViewById(R.id.btDown);
		btnLeft = (Button) findViewById(R.id.btLeft);
		btnRight = (Button) findViewById(R.id.btRight);
		
		btnUp.setOnClickListener(this);
		btnDown.setOnClickListener(this);
		btnLeft.setOnClickListener(this);
		btnRight.setOnClickListener(this);
		btnStop.setOnClickListener(this);
		btn10Speed.setOnClickListener(this);
		btn20Speed.setOnClickListener(this);
		btn30Speed.setOnClickListener(this);
		btn40Speed.setOnClickListener(this);
		btn50Speed.setOnClickListener(this);
		btn60Speed.setOnClickListener(this);
		btn70Speed.setOnClickListener(this);
		btn80Speed.setOnClickListener(this);
		btn90Speed.setOnClickListener(this);
		btn100Speed.setOnClickListener(this);

«Описали» кнопки. Дальше, загружаем в переменную address MAC BT адаптера нашей тележки, если она была сохранена в настройках.


		sPref = getSharedPreferences("RobotPref", MODE_PRIVATE);
		address = sPref.getString(SAVED_MAC, "");
		Toast.makeText(getBaseContext(), "Подключение к адресу: " + address, Toast.LENGTH_LONG).show();
		
		bl = new cBluetooth(this, mHandler);
	    bl.checkBTState();
	    
	}
	

Дальше, шлем в зависимости от нажатых кнопок на экране, коды нашему роботу. Тут можно поменять коды под любое свое устройство.


	@Override
	public void onClick(View v) {
		switch (v.getId()) 
		{
			case R.id.btUp:
				if(BT_is_connect) bl.sendData("8");
				break;
			case R.id.btDown:
				if(BT_is_connect) bl.sendData("2");
				break;
			case R.id.btLeft:
				if(BT_is_connect) bl.sendData("4");
				break;
			case R.id.btRight:
				if(BT_is_connect) bl.sendData("6");
				break;
			case R.id.btStop:
				if(BT_is_connect) bl.sendData(" ");
				break;
			case R.id.bt10Speed:
				if(BT_is_connect) bl.sendData("Q");
				break;
			case R.id.bt20Speed:
				if(BT_is_connect) bl.sendData("W");
				break;
			case R.id.bt30Speed:
				if(BT_is_connect) bl.sendData("E");
				break;
			case R.id.bt40Speed:
				if(BT_is_connect) bl.sendData("R");
				break;
			case R.id.bt50Speed:
				if(BT_is_connect) bl.sendData("T");
				break;
			case R.id.bt60Speed:
				if(BT_is_connect) bl.sendData("Y");
				break;
			case R.id.bt70Speed:
				if(BT_is_connect) bl.sendData("U");
				break;
			case R.id.bt80Speed:
				if(BT_is_connect) bl.sendData("I");
				break;
			case R.id.bt90Speed:
				if(BT_is_connect) bl.sendData("O");
				break;
			case R.id.bt100Speed:
				if(BT_is_connect) bl.sendData("P");
				break;
			
		}
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.button, menu);
		return true;
	}
	
	private static class MyHandler extends Handler {
        private final WeakReference<ButtonActivity> mActivity;
     
        public MyHandler(ButtonActivity activity) {
          mActivity = new WeakReference<ButtonActivity>(activity);
        }
     
        @Override
        public void handleMessage(Message msg) {
        	ButtonActivity activity = mActivity.get();
        	if (activity != null) {
        		switch (msg.what) {
	            case cBluetooth.BL_NOT_AVAILABLE:
	            	Toast.makeText(activity.getBaseContext(), "Bluetooth is not available", Toast.LENGTH_SHORT).show();
	                activity.finish();
	                break;
	            case cBluetooth.BL_INCORRECT_ADDRESS:
	            	Toast.makeText(activity.getBaseContext(), "Incorrect Bluetooth address", Toast.LENGTH_SHORT).show();
	                break;
	            case cBluetooth.BL_REQUEST_ENABLE:   
	            	BluetoothAdapter.getDefaultAdapter();
	            	Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
	            	activity.startActivityForResult(enableBtIntent, 1);
	                break;
	            case cBluetooth.BL_SOCKET_FAILED:
	            	Toast.makeText(activity.getBaseContext(), "Socket failed", Toast.LENGTH_SHORT).show();
	            	activity.finish();
	                break;
	          	}
          	}
        }
	}
	
	private final MyHandler mHandler = new MyHandler(this);

Если свернули приложение или сменили ориентацию экрана восстанавливаем коннект с роботом.


	@Override
    protected void onResume() {
    	super.onResume();
    	
    	BT_is_connect = bl.BT_Connect(address, false);
    }

    @Override
    protected void onPause() {
    	super.onPause();
    	bl.BT_onPause();
    }

}


Адрес подключения сохраняется в настройках. Можно прописывать руками, можно выбирать из найденого или списка доверенных устройств bluetooth.




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textMAC"
        android:layout_width="match_parent"
        android:layout_height="75px"
        android:text="Текущий МАС адрес по умолчанию для подключения:"
        android:textColor="#FFFFFF" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="75px"
        android:text="Ведите MAC адрес по умолчанию для подключения (XX:XX:XX:XX:XX:XX): " 
        android:textColor="#FFFFFF"/>

    <EditText
        android:id="@+id/editMAC"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:textColor="#FFFFFF" >

        <requestFocus />
    </EditText>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center" >

        <Button
            android:id="@+id/btSave"
            android:layout_width="250px"
            android:layout_height="75px"
            android:text="Сохранить" 
            android:textColor="#FFFFFF"/>

        <Button
            android:id="@+id/btCancel"
            android:layout_width="250px"
            android:layout_height="75px"
            android:text="Отмена" 
            android:textColor="#FFFFFF"/>

    </LinearLayout>

    <Button
        android:id="@+id/btFindMAC"
        android:layout_width="500px"
        android:layout_height="75px"
        android:layout_gravity="center"
        android:text="Найти MAC" 
        android:textColor="#FFFFFF"/>

</LinearLayout>



package com.example.btrobotfull;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class PreferencesActivity extends Activity implements OnClickListener {
	
	EditText etMAC;
	
	TextView txtMAC;
	
	Button btnSave;
	Button btnCancel;
	Button btnFindMAC;
	
	SharedPreferences sPref;
	final String SAVED_MAC = "00:00:00:00:00:00";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.preferences_tab3);
		
		btnSave = (Button) findViewById(R.id.btSave);
		btnCancel = (Button) findViewById(R.id.btCancel);
		btnFindMAC = (Button) findViewById(R.id.btFindMAC);
		btnSave.setOnClickListener(this);
		btnCancel.setOnClickListener(this);
		btnFindMAC.setOnClickListener(this);
		
		txtMAC = (TextView) findViewById(R.id.textMAC);
		
		etMAC = (EditText) findViewById(R.id.editMAC);
		
		sPref = getSharedPreferences("RobotPref", MODE_PRIVATE);
		String savedText = sPref.getString(SAVED_MAC, "");
		txtMAC.setText("Текущий МАС адрес по умолчанию для подключения: " + savedText);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.preferences, menu);
		return true;
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.btSave:
			saveText();
			break;
		case R.id.btCancel:
			finish();
			break;
		case R.id.btFindMAC:
			Intent intent_bt = new Intent(this, BTActivity.class);
	    	startActivity(intent_bt);
			break;
		default:
			break;
		}
		
	}
	
	void saveText() {
		sPref = getSharedPreferences("RobotPref", MODE_PRIVATE);
		Editor ed = sPref.edit();
		ed.putString(SAVED_MAC, etMAC.getText().toString());
		ed.commit();
		String savedText = sPref.getString(SAVED_MAC, "");
		txtMAC.setText("Текущий МАС адрес по умолчанию для подключения: " + savedText);
		}
	
	@Override
    protected void onResume() {
    	super.onResume();
    	
    	sPref = getSharedPreferences("RobotPref", MODE_PRIVATE);
    	String savedText = sPref.getString(SAVED_MAC, "");
    	txtMAC.setText("Текущий МАС адрес по умолчанию для подключения: " + savedText);
    }


}


Список доверенных и найденных устройств bluetooth. Нажатие на любого из списка, заносит его в настройки по умолчанию для дальнейших подключений.




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textPairedDevices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Доверенные устройства"
        android:textColor="#FFFFFF" />

    <ListView
        android:id="@+id/listPairedDevices"
        android:layout_width="match_parent"
        android:layout_height="300px"
        android:background="#615e54"
        android:textColor="#FFFFFF" >

    </ListView>

    <TextView
        android:id="@+id/textNewDevices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Новые устройства"
        android:textColor="#FFFFFF" />

    <ListView
        android:id="@+id/listNewDevices"
        android:layout_width="match_parent"
        android:layout_height="300px"
        android:background="#615e54"
        android:textColor="#FFFFFF" >

    </ListView>

    <Button
        android:id="@+id/btScan"
        android:layout_width="match_parent"
        android:layout_height="75px"
        android:text="Сканировать"
        android:textColor="#FFFFFF" />

</LinearLayout>



package com.example.btrobotfull;

import java.util.Set;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

public class BTActivity extends Activity implements OnClickListener {
	
	public static String EXTRA_DEVICE_ADDRESS = "device_address";
	
    private BluetoothAdapter mBluetoothAdapter;
    private ArrayAdapter<String> mPairedDevicesArrayAdapter;
    private ArrayAdapter<String> mNewDevicesArrayAdapter;
    
    private String info;
    private String address;
    
    ListView lvPairedDevices;
    ListView lvNewDevices;
    
    SharedPreferences sPref;
	final String SAVED_MAC = "00:00:00:00:00:00";
    
    Button btnScan;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.bt_tab3);
		
		lvPairedDevices = (ListView) findViewById(R.id.listPairedDevices);
		lvNewDevices = (ListView) findViewById(R.id.listNewDevices);
		
		btnScan = (Button) findViewById(R.id.btScan);
		btnScan.setOnClickListener(this);
		
		mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
		mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name);
		
		lvPairedDevices.setOnItemClickListener(mDeviceClickListener);
		lvNewDevices.setOnItemClickListener(mDeviceClickListener);
		
		lvPairedDevices.setAdapter(mPairedDevicesArrayAdapter);	
		lvNewDevices.setAdapter(mNewDevicesArrayAdapter);
		
		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        this.registerReceiver(mReceiver, filter);
        
        filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        this.registerReceiver(mReceiver, filter);
		
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		
		Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
		if (pairedDevices.size() > 0) {
		    for (BluetoothDevice device : pairedDevices) {
		    	mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
		    }
		}
			
	}
	
	@Override
	public void onClick(View arg0) {
		doDiscovery();
		
	}
	
	@Override
    protected void onDestroy() {
        super.onDestroy();

        if (mBluetoothAdapter != null) {
        	mBluetoothAdapter.cancelDiscovery();
        }

        this.unregisterReceiver(mReceiver);
    }
	
	private void doDiscovery() {
		
		setProgressBarIndeterminateVisibility(true);

        if (mBluetoothAdapter.isDiscovering()) {
        	mBluetoothAdapter.cancelDiscovery();
        }

        mBluetoothAdapter.startDiscovery();
    }
	
	private OnItemClickListener mDeviceClickListener = new OnItemClickListener() {
        public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {

        	mBluetoothAdapter.cancelDiscovery();

            info = ((TextView) v).getText().toString();
            address = info.substring(info.length() - 17);
            
            sPref = getSharedPreferences("RobotPref", MODE_PRIVATE);
            Editor ed = sPref.edit();
            ed.putString(SAVED_MAC, address);
            ed.commit();
            String savedText = sPref.getString(SAVED_MAC, "");

            Toast.makeText(getBaseContext(),"Установлен адрес подключения по умолчанию: " + savedText, Toast.LENGTH_LONG).show();

            finish();
        }
    };
	
	private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
                }
            }
        }
    };

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.bt, menu);
		return true;
	}

}



Еще понадобился дополнительный класс:


package com.example.btrobotfull;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build;
import android.os.Handler;


public class cBluetooth {
	
	private BluetoothSocket btSocket;
    private OutputStream outStream;
    private BluetoothAdapter mBluetoothAdapter;
    private ConnectedThread mConnectedThread;
    
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    
    private final Handler mHandler;
    public final static int BL_NOT_AVAILABLE = 1;  
    public final static int BL_INCORRECT_ADDRESS = 2;	
    public final static int BL_REQUEST_ENABLE = 3;		
    public final static int BL_SOCKET_FAILED = 4;	
    public final static int RECIEVE_MESSAGE = 5;
    
    cBluetooth(Context context, Handler handler){
    	mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    	mHandler = handler;
        if (mBluetoothAdapter == null) {
        	mHandler.sendEmptyMessage(BL_NOT_AVAILABLE);
            return;
        }
    }
    
    public void checkBTState() {
    	if(mBluetoothAdapter == null) { 
     		mHandler.sendEmptyMessage(BL_NOT_AVAILABLE);
    	} else {
    		if (!mBluetoothAdapter.isEnabled()) {
    			mHandler.sendEmptyMessage(BL_REQUEST_ENABLE);
    		}
    	}
	}
    
    private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
        if(Build.VERSION.SDK_INT >= 10){
            try {
                final Method  m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class });
                return (BluetoothSocket) m.invoke(device, MY_UUID);
            } catch (Exception e) { }
        }
        return  device.createRfcommSocketToServiceRecord(MY_UUID);
    }
    
    public boolean BT_Connect(String address, boolean listen_InStream) {   	

    	boolean connected = false;
    	
    	if(!BluetoothAdapter.checkBluetoothAddress(address)){
    		mHandler.sendEmptyMessage(BL_INCORRECT_ADDRESS);
    		return false;
    	}
    	else{
    		    		
    		BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    		try {
				btSocket = createBluetoothSocket(device);
			} catch (IOException e1) {
	        	mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	    		return false;
			}
	        
    		mBluetoothAdapter.cancelDiscovery();
	        
	        try {
	          btSocket.connect();
	        } catch (IOException e) {
	          try {
	            btSocket.close();
	          } catch (IOException e2) {
	        	  mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	        	  return false;
	          }
	        }
	     
	        try {
	        	outStream = btSocket.getOutputStream();
	        	connected = true;
	        } catch (IOException e) {
	        	mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	        	return false;
	        }
			if(listen_InStream) {
				mConnectedThread = new ConnectedThread();
		    	mConnectedThread.start();
    		}
    	}
    	return connected;
	}
    
    public void BT_onPause() {
    	if (outStream != null) {
    		try {
    	        outStream.flush();
    	    } catch (IOException e) {
	        	mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	        	return;
    	    }
    	}

    	if (btSocket != null) {
	    	try {
	    		btSocket.close();
	    	} catch (IOException e2) {
	        	mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	        	return;
	    	}
    	}
    }
    
    public void sendData(String message) {
        byte[] msgBuffer = message.getBytes();
     
        if (outStream != null) {
	        try {
	        	outStream.write(msgBuffer);
	        } catch (IOException e) {
	        	mHandler.sendEmptyMessage(BL_SOCKET_FAILED);
	        	return;      
	        }
        }
	}
    
    private class ConnectedThread extends Thread {

    	private final InputStream mmInStream;
		 
    	public ConnectedThread() {
    		InputStream tmpIn = null;

    		try {
    			tmpIn = btSocket.getInputStream();
    		} catch (IOException e) {}
		 
    		mmInStream = tmpIn;
    	}
		 
    	public void run() {
    		byte[] buffer = new byte[256]; 
    		int bytes; 				

    		while (true) {
    			try {
    				bytes = mmInStream.read(buffer);
    				mHandler.obtainMessage(RECIEVE_MESSAGE, bytes, -1, buffer).sendToTarget();
    			} catch (IOException e) {
    				break;
    			}
    		}
    	}
    }

}


«Мозг» будущей тележки на основе stm32 сигналы из программы принимает адекватно, радостно отвечает помаргиванием и текстом на LCD. Обработкой полученных сигналов занимается обработчик прерывания:


void USART2_IRQHandler(void) 
{
	if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) 
	{
    USART_ClearITPendingBit(USART2, USART_IT_RXNE);
		UART2IRQbuf[UART2Count] = USART2->DR;		
		if (!ConfigMode)
		{
			switch (UART2IRQbuf[UART2Count])
			{
				case 81:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 87:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 69:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 82:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 84:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 89:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 85:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 73:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 79:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 80:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 32:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 61:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 45:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 56:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 50:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 52:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case 54:
					CommandChar = UART2IRQbuf[UART2Count];
					UART2Count = -1;
					isr_evt_set (EVT_MOVE_COMMAND, t_MOVE);
					break;
				case _CR:
					strlcpy(Command, UART2IRQbuf, UART2Count+1);
					UART2Count = -1;
					isr_evt_set (EVT_TERM_COMMAND, t_TERMINAL);
					break;
				case _ESC:
					ConfigMode = 1;
					MotorEnable = 0;
					MotorsStop ();
					UART2Count = -1;
					Usart2_putString ("\n\n<<<< CONFIG MODE >>>\n\n");
					break;
			}
		}
		else
		{
			switch (UART2IRQbuf[UART2Count])
			{
				case _ESC:
					ConfigMode = 0;
					MotorEnable = 1;
					UART2Count = -1;
					Usart2_putString ("\n\n<<<< MOVE MODE>>>\n\n");
					break;
				case _CR:
					strlcpy(Command, UART2IRQbuf, UART2Count+1);
					UART2Count = -1;
					isr_evt_set (EVT_CONFIG_COMMAND, t_CONFIG);
					break;
			}
		}

		UART2Count++;
		if ( UART2Count == UART2SIZE )
		{
			UART2Count = 0;		
		}
  } 
}



Который дает сигнал задачам KEIL RTOS на обработку.


__task void f_MOVE (void)
{	
	uint8_t c[2] = "0";
	while (1)
	{
		os_evt_wait_or (EVT_MOVE_COMMAND, 0xffff);
		
		sprintf (c, "%02d", CommandChar);
		LCDgotoxy(7,0);
		LCDputs (C);
		
		switch (CommandChar)
		{
			case 32:
				MotorsStop ();
				break;
			case 61:
				MotorSpeedUp ();
				break;
			case 45:
				MotorSpeedDown ();
				break;
			case 56:
				MoveForward ();
				break;
			case 50:
				MoveBack ();
				break;
			case 52:
				MoveLeft ();
				break;
			case 54:
				MoveRight ();
				break;
			case 81:
				Motor2Speed = 60;
				Motor4Speed = 60;
				break;
			case 87:
				Motor2Speed = 120;
				Motor4Speed = 120;
				break;
			case 69:
				Motor2Speed = 180;
				Motor4Speed = 180;
				break;
			case 82:
				Motor2Speed = 240;
				Motor4Speed = 240;
				break;
			case 84:
				Motor2Speed = 300;
				Motor4Speed = 300;
				break;
			case 89:
				Motor2Speed = 360;
				Motor4Speed = 360;
				break;
			case 85:
				Motor2Speed = 420;
				Motor4Speed = 420;
				break;
			case 73:
				Motor2Speed = 480;
				Motor4Speed = 480;
				break;
			case 79:
				Motor2Speed = 540;
				Motor4Speed = 540;
				break;
			case 80:
				Motor2Speed = 600;
				Motor4Speed = 600;
				break;
			
		}
	}
}




Пока не работают управление пальцем по экрану и акселерометр. Это уже на будущее, когда тележка будет собрана, что бы было реальное устройство для отладки.

PS. Картинки уменьшить не могу.
  • +4
  • 25 октября 2013, 22:15
  • lexanet
  • 1
Файлы в топике: BTRobotFull.zip

Комментарии (13)

RSS свернуть / развернуть
lexanet , простите за то что начну комментарии с замечания:
мне, как человеку мало что понимающему в программировании под Android, все таки хотелось бы видеть пояснения к выше приведенным строкам кода. Потому как не вижу смысла его(код) приводить непосредственно в статье без пояснений(комментариев) — тогда уж добавить в архив и прикрепить к посту.
0
  • avatar
  • Zov
  • 26 октября 2013, 01:01
Как абсолютно такой же далекий от андроида человек, могу сказать, комментарии кода тут не помогут. Если это «первый раз», чтение книг и написание собственного «Hello World» просто необходимы. Иначе, даже про подключение одной кнопки придется целую статью писать. Но, наверно Вы в чем то правы. Трудновато читать. Разобью код на блоки и добавлю комментарии в текст.
А еще, рекомендую: http://startandroid.ru/ru/uroki/vse-uroki-spiskom.html. На русском и доступным языком с самых азов.
PS. Архив с кодом конечно прилеплен :)
+1
Ок)
0
Воу-воу-воу, паллехче!
Тут в редакторе можна установить на эти все портянки скрытия текста. И он будет розворачеватся при нажатии на плюсик.
Даже не стал читать, увидев столько портянок.
Хотя статейка должна быть полезной.
Я как то пробовал на Visual C# писать, порт фреймвлрка Моно, но меня это утомило и забросил.
0
Какой тег нужно приписать к коду, чтобы он разворачивался по плюсику? Не нашел.
0
Упс, извините что путаю вас. Эта фигня на форуме есть, а не здесь.
0
Ничего. Я потихоньку разбиваю портянки на кусочки, чтобы добавить немного комментариев. Думаю, так будет легче воспринимать.
0
просмотрел, всё классно. кажется, не хватает только быстрой инструкции по установке софта на андроид и быстрого описания протокола — для тех, кто будет делать начинку на другом мк, и хотел бы воспользоваться готовым андроид-клиентом без вникания в код. впрочем, по onclick вроде бы понятно — оно отрабатывает только однократные касания?
0
Если вникать нет необходимости, то можно установить RC управление с маркета. Там полно бесплатных программ для управления машинками по bluetooth.
Да, вся настройка под свою начинку, это коды отправляемых кнопок. Счас немного переделаю код, добавлю двухстороннюю связь и продолжу разбивать портянки и добавлять комменты.
Как все это работает в живую я не знаю. Контроллер готов, прошивка написана, прога для управления с телефоном тоже, а самой тележки еще нет. Несут почтальоны. Поэтому, все на уровне догадок и предположений. Контроллер, конечно, снабжен системой индикации и экранчик к нему прилеплен, но, реальной картины это не даст.
0
они там тоже грешат тем, что как правило поставляются as is и без описания протоколов :-(
и вообще, чтобы найти в плеймаркете что-то нужное и полностью покрывающее потребности — нужно проявить чудеса выдержки.
в данном случае прогнозирую возможное желание сделать так, чтобы тележка двигалась только до тех пор, пока палец касается экрана. а еще круче — разбить экран на 8 секторов и при перемещении пальца по секторам — пускай меняются значения (направления движения относительно оси тележки), палец отпущен — движение остановлено.
кстати, вот бы для андроида еще заиметь приложение, чтобы через блютус серийный порт получать пачки значений и выводить в риалтайме на стрелочных циферблатах…
0
о, а чем дальше палец уведён от центра экрана, тем выше скорость тележки (в заданном направлении)! уау! я дизайнер интерфейсов! :=)
0
Вот у «того парня», ссылку на которого я приводил в самом начале, вроде, реализовано управление пальцем по экрану. Я тоже собираюсь такое сделать. Но, потом :)
А протокол, так он простой и у всех один, кто управляет по синезубу (удлинителю уарт-а по сути). Кнопку на анрофоне жмешь, onClick посылает ASCII код назначенной буквы в эфир:

case R.id.btUp:
if(BT_is_connect) bl.sendData("8");
break;


На машинке или бесконечный цикл или прерывание слетят за входящими символами, поступающими по уарт-у и в зависимости от кода символа что то делают:

case 56:
MoveForward ();
break;


И все. Любому нужно просто или поменять символы в noClick под свою существующую тележку или в прошивке тележки поменять код символа, на приход которого нужно реагировать.
Палец на кнопке — едем, палец подняли — остановились, это уже в прошивке тележки лучше реализовать, чтобы она не убежала от пользователя, когда связь пропадет :) Грубо говоря, как то так:

void MoveForward (void)
{
	if (MotorEnable)
	{
		LeftMotorForward ();
		RightMotorForward ();
		
		TIM3_OCInitStructure.TIM_Pulse = Motor2Speed;
		TIM_OC2Init(TIM3, &TIM3_OCInitStructure);
		
		TIM3_OCInitStructure.TIM_Pulse = Motor4Speed;
		TIM_OC4Init(TIM3, &TIM3_OCInitStructure);
		
		Delay_MS (500);
		
		TIM3_OCInitStructure.TIM_Pulse = 0;
		TIM_OC2Init(TIM3, &TIM3_OCInitStructure);
		
		TIM3_OCInitStructure.TIM_Pulse = 0;
		TIM_OC4Init(TIM3, &TIM3_OCInitStructure);
	}
}

Т.е. нажатие словили, тележка какое то время едет и останавливается до следующего нажатия. Палец на кнопке, сигналы нажатия идут непрерывно, тележка постоянно едет. Палец с кнопкт убрали, тележка остановилась через заданное время.
По уарту можно получать все что угодно. Хоть роман Толстова. Была бы возможность разобрать его на составляющие :) У меня с тележки на телефон каждую секунду отсылается строка:

08:45:07, 12.2V, 000-000>


Значение RTC, напряжение аккумулятора и значения PWM левого и правого мотора. Я это разбиваю и вывожу в окна управления на андроиде.
0
ну вот не у всех так. колупался в джава-коде кривой j2me управлялки для нокии — там в зависимости от нажатия/отпускания изменялись биты в постоянно передаваемом байте.
какие-то клиенты отслеживают отпускание кнопки, какие-то нет. не говоря уже о том, что каждый клиент отправляет свои символы.
такую реализацию, как предложена выше (Т.е. нажатие словили, тележка какое то время едет и останавливается до следующего нажатия. Палец на кнопке, сигналы нажатия идут непрерывно, тележка постоянно едет. Палец с кнопкт убрали, тележка остановилась через заданное время.) уже реализовывал — насколько я помню, были какие-то неустранимые дергания. в итоге, насколько помню, перешел на алгоритм без проверки на отвал блютуса…
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.