[Arduino+Android] 自制土砲智能安全帽

时间:2023-03-08 17:54:25

专案动机(1/2)

.现今社会中,各种交通运输载具方便了人类的生活,缩小了地域的差异性,当中车辆是人们日常生活中最频繁接触到的一部分。
.车辆使人们的行动更加便利,也因此道路上行驶的车辆越来越多。
.路况的复杂性也因此添加,行车安全的问题日趋重要。
.道路上危急事故的产生,除了是因为驾驶人本身道路安全观念不足造成,也可能是因为驾驶人本身对车辆的控制力不够而造成危急。

专案动机(2/2)

.在驾驶人不具备职业车手之技能与意识的前提下。要维持对车体良好安定的控制。最稳妥的条件就是保持安全稳定的车速。以避免不可抗力之意外发生。
.因此驾驶人须要能随时迅速的得知车辆的实际车速,而非单纯凭藉人体对速度的感觉来操控车辆。
.考虑到二轮机车通常不具备挡风玻璃来实装抬头显示器(HUD)以显示车速,本专题将实作能显示即时车速的智慧安全帽,降低视线转移至仪表板以及身体重心变化而发生危急的机会。

系统功能

1.以行动装置得到GPS定位数据并算出即时车速
2.于安全帽中显示即时车速
3.车速超过70km时发出危急警报

硬体清单

1.四分之三安全帽 * 1
2.Arduino UNO * 1
3.蓝芽模组(HC-06从机) *1
4.Android手机 * 1
5.74HC595串列式七段显示器模组 * 1
6.蜂鸣器 *1
7.USB(Type A)母接头 *1
8.镜面反射膜 *1
9.各种线材与废物利用

GPS測速

.利用行动装置的GPS定位依据车辆行驶中不断变化位置,持续得到某两个点的位置数据以及两点之间的时间差来计算出行车速度数据。(採用Android
API)

.在卫星讯号稳定、车辆稳定行驶、路面斜率不大的情况下,GPS所測得速度差点儿等于真实速度。

GPS測速与仪表板之误差

.在实际測试中,车辆稳定行驶的情况下,当车速维持30~40km/h时。測试用机车之仪表板所显示速度比GPS定位測得之车速平均高7 
km/h左右。
.原因:

         依据*交通部车辆安全检測基准,以无载条件(空车重+驾驶员+必要仪器)且依下列速率測试。指示速率必须永不少于真实速率且速率计标度盘指示之速率(V1)与真实速率(V2)间应满足关系:

          0 <= ( V1 - V2 ) <= ( V2 / 10) +4 km/h

.故车速越快,在同意范围内的误差就可能越大。



即时车速显示

.行动装置上之Android程式计算出的车速通过蓝芽模组将数据传送给Arduino。
.採用74HC595串列式七段显示器模组显示数据,于安全帽内(考虑到防雨)投射速度数据于反射膜以反射给眼睛。

[Arduino+Android] 自制土砲智能安全帽

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWVoam9yZGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="302" height="160" alt="">[Arduino+Android] 自制土砲智能安全帽

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWVoam9yZGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="214" height="167" alt="">

上方图示为HC-06蓝芽模组(左)、74HC595串列式七段显示器模组(右)



[Arduino+Android] 自制土砲智能安全帽
上图为安全帽内部效果展示

[Arduino+Android] 自制土砲智能安全帽[Arduino+Android] 自制土砲智能安全帽

反射投影

.当物体距离眼睛太近。便无法准确使物体在视网膜上清晰成像。
.为了能清楚看到物体。必须延长物距。
.安全帽内空间有限,镜面反射成像是简单有效的方案。
[Arduino+Android] 自制土砲智能安全帽

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWVoam9yZGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

透过反射投影,在安全帽有限空间内理想视距最大可达到两倍

[Arduino+Android] 自制土砲智能安全帽

系统架构

[Arduino+Android] 自制土砲智能安全帽

装置电路图

[Arduino+Android] 自制土砲智能安全帽[Arduino+Android] 自制土砲智能安全帽

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWVoam9yZGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

遭遇问题

.因为安全帽挡风镜非平面。若将反射膜直接贴于挡风镜上。以反射式投影会使影像看起来不平整。
.解决方案:

         为反射膜制作独立基座,使反射膜保持平整,并与视线尽可能保持垂直。

[Arduino+Android] 自制土砲智能安全帽

[Arduino+Android] 自制土砲智能安全帽

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveWVoam9yZGFu/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

装置组装完毕图

[Arduino+Android] 自制土砲智能安全帽

实际展示影片

Youtube:

http://youtu.be/qU20tuYm1ZA

优酷:

http://v.youku.com/v_show/id_XNzI5MzE2Njcy.html

(因为反射膜为半透明非全反射,会透光。因此从外部所看到七段显示器的数字会是颠倒的。)

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

源代碼

最后附上本专案所用到的源码,project我就不打包了。以下已经差点儿包含所有的东西



Android 行动装置端主程序

package com.helmethud;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID; import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Criteria;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast; public class HelmetHud extends Activity { // 除錯用標記
private static final String TAG = "HELMETHUDCLIENT";
private static final boolean D = true; // Bluetooth相關變量
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothSocket btSocket = null;
private OutputStream outStream = null;
// Well known SPP UUID (will *probably* map to
// RFCOMM channel 1 (default) if not in use);
// see comments in onResume().
private static final UUID MY_UUID = UUID
.fromString("00001101-0000-1000-8000-00805F9B34FB"); // ==> hardcode bluetooth server's MAC address here <==
// private static String address = "98:D3:31:40:03:34";
private boolean BTIsConnected = false; // 藍芽是否已連線flag
private boolean hasFirstMatch = false; // 藍芽是否已經過第一次配對flag
private boolean ReceiverIsRegisted = false; // BroadcastReceiver是否已註册flag // 線程HANDLE相關變量
private Handler connectBTHandler = new Handler();
private Handler sendBTMsgHandler = new Handler();
private Handler sendBTTestMsgHandler = new Handler();
private Handler hudThreadHandler = new Handler();
private Handler getSpeedThreadHandler = new Handler();
Handler msgHandler; // UI相關變量
private Button btnDiscovery, btnConnect, btnSend, btnStartHud;
private Spinner spinner;
private ArrayAdapter<String> arrayAdapterDeviceName;
private static final String[][] devicesStr = { { "Plz select device...", "" } };
private List<String> allDevices, allDevicesAddress;
private int selectedAddress = 0; // GPS定位相關變量
final String SERVERNAME = Context.LOCATION_SERVICE;
LocationManager locationManager;
float speed = 0, lastSpeed = 0; // 車速
int satelliteCount = 0; // 當前衛星個數
Date gpdDate = new Date();
StringBuilder tempBuilder = new StringBuilder();
TextView tvGPSState, tvGPSInfo;
private static final int STATIC_CONTINUED_TIME = 6000; // 判斷靜止持續時間(毫秒) EditText et1; // 测試用輸入框 private boolean IsTesting = false; // 程序執行是否為發送测試訊息
int sum = 0; // SendMsgThread傳送藍芽訊息線程執行總次數
private static final int BT_RECONNECT_LIMIT_TIME = 300; // 線程最大允許執行次數;值訂為300以防止低階設備效能不足而出錯
private static final int NEED_BT_RECONNECT_DEVICE_MAX_SDK = 13; // 须要又一次連線的裝置Android_SDK_API最大版本号 /** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_helmet_hud);
setTitle("Not connected"); et1 = (EditText) findViewById(R.id.editText1);
btnDiscovery = (Button) findViewById(R.id.btnDiscovery);
btnConnect = (Button) findViewById(R.id.btnConnect);
btnSend = (Button) findViewById(R.id.btnSend);
spinner = (Spinner) findViewById(R.id.spinner1);
allDevices = new ArrayList<String>();
for (int i = 0; i < devicesStr.length; i++) {
allDevices.add(devicesStr[i][0]);
}
allDevicesAddress = new ArrayList<String>();
for (int i = 0; i < devicesStr.length; i++) {
allDevicesAddress.add(devicesStr[i][1]);
}
/* new ArrayAdapter物件並將allDevices傳入 */
arrayAdapterDeviceName = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, allDevices);
arrayAdapterDeviceName
.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(arrayAdapterDeviceName);
btnStartHud = (Button) findViewById(R.id.btnStartHud);
// btnStartHud.setEnabled(false); //测試時保持可用
tvGPSState = (TextView) findViewById(R.id.tvGPSState);
tvGPSInfo = (TextView) findViewById(R.id.tvGPSInfo); btnDiscovery.setOnClickListener(new OnClickListener() { @Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
Log.e(TAG, "Cancel discovery");
} mBluetoothAdapter.startDiscovery(); // 开始搜尋裝置 }
}); btnConnect.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
connectBTHandler.post(ConnectBTThread);
}
}); btnSend.setOnClickListener(new OnClickListener() { @Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
IsTesting = true;
sendBTTestMsgHandler.post(SendBTTestMsgThread);
}
}); spinner.setOnItemSelectedListener(new OnItemSelectedListener() { @Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
// TODO Auto-generated method stub
parent.setVisibility(View.VISIBLE);
selectedAddress = position;
} @Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub }
}); btnStartHud.setOnClickListener(new OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
IsTesting = false;
hudThreadHandler.post(HudThread);
btnStartHud.setEnabled(false);
}
}); msgHandler = new Handler() { @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0) { // 結束發送测試訊息線程並又一次連接藍芽以重置(避免發送訊息過多而當機)
sendBTTestMsgHandler.removeCallbacks(SendBTTestMsgThread);
Log.e(TAG, "removecallbacks!");
// 關閉藍芽通道
if (outStream != null) {
try {
outStream.flush();
outStream = null;
} catch (IOException e) {
Log.e(TAG,
"ON PAUSE: Couldn't flush output stream.",
e);
}
}
try {
if (BTIsConnected) {
BTIsConnected = false;
btSocket.close();
btSocket = null;
}
} catch (IOException e2) {
Log.e(TAG, "ON PAUSE: Unable to close socket.", e2);
}
// 又一次連線藍芽
sum = 0;
Log.e(TAG, "Reconnecting...");
connectBTHandler.post(ConnectBTThread);
} else if (msg.what == 1) { // 開啟發送测試訊息線程
Log.e(TAG, "post");
sendBTTestMsgHandler.post(SendBTTestMsgThread);
} else if (msg.what == 2) { // 又一次連線藍芽
Log.e(TAG, "Reconnecting...");
connectBTHandler.post(ConnectBTThread);
} }
}; // TODO: Bluetooth Initial
if (D)
Log.e(TAG, "+++ ON CREATE +++"); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this, "Bluetooth is not available.",
Toast.LENGTH_LONG).show();
finish();
return;
} if (!mBluetoothAdapter.isEnabled()) {
Toast.makeText(this,
"Please enable your BT and re-run this program.",
Toast.LENGTH_LONG).show();
finish();
return;
} if (D)
Log.e(TAG, "+++ DONE IN ON CREATE, GOT LOCAL BT ADAPTER +++"); // 註册一個BroadcastReceiver,等等會用來接收搜尋到裝置的消息
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mReceiver, filter);
ReceiverIsRegisted = true;
} private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
BluetoothDevice device = null;
// 當收尋到裝置時
if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
Log.e(TAG, "Discovered");
// 取得藍芽裝置這個物件
device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 取得裝置名稱字串
String newDeviceName = device.getName().toString();
// 取得裝置MAC地址
String newDeviceAddress = device.getAddress().toString();
// 先比较加入的值是否已存在。不存在才可加入入Spinner選單
for (int i = 0; i < arrayAdapterDeviceName.getCount(); i++) {
if (newDeviceName.equals(arrayAdapterDeviceName.getItem(i))) {
return;
}
}
if (!newDeviceName.equals("")) {
arrayAdapterDeviceName.add(newDeviceName); // 將值加入到adapter
allDevicesAddress.add(newDeviceAddress); // 將地址加入到ArrayList
arrayAdapterDeviceName.notifyDataSetChanged();
}
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent
.getAction())) {
device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING:
Log.e(TAG, "Bluetooth device is pairing...");
break;
case BluetoothDevice.BOND_BONDED:
Log.e(TAG, "Pairing complete");
break;
case BluetoothDevice.BOND_NONE:
Log.e(TAG, "Cancel paring");
break;
default:
break;
}
}
}
}; Runnable ConnectBTThread = new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub // Blocking connect, for a simple client nothing else can
// happen until a successful connection is made, so we
// don't care if it blocks.
if (selectedAddress == 0) {
return;
} // 停止搜尋以減少資源消耗
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
Log.e(TAG, "Cancel discovery");
} // 連結到該裝置
BluetoothDevice device = mBluetoothAdapter
.getRemoteDevice(allDevicesAddress.get(selectedAddress)
.toString());
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e(TAG, "Connect: Socket creation failed.", e);
}
try {
btSocket.connect();
BTIsConnected = true;
Log.e(TAG,
"ON RESUME: BT connection established, data transfer link open.");
} catch (IOException e) {
try {
btSocket.close();
BTIsConnected = false;
} catch (IOException e2) {
Log.e(TAG,
"ON RESUME: Unable to close socket during connection failure",
e2);
}
}
Log.e(TAG, "BTisConnected = " + Boolean.toString(BTIsConnected));
Message msg = msgHandler.obtainMessage();
if (!BTIsConnected) {
btnSend.setEnabled(false);
btnStartHud.setEnabled(false);
setTitle("Not connected");
Log.e(TAG, "Try reconnect");
msg.what = 2;
msgHandler.sendMessage(msg); // 又一次連線
} else {
btnSend.setEnabled(true);
btnStartHud.setEnabled(true);
setTitle("Connected");
// Create a data stream so we can talk to server.
if (D)
Log.e(TAG, "+ ABOUT TO SAY SOMETHING TO SERVER +");
if (IsTesting && hasFirstMatch) {
msg.what = 1;
msgHandler.sendMessage(msg); // 已又一次連線。恢復發送测試訊息
}
hasFirstMatch = true;
}
}
}; Runnable SendBTMsgThread = new Runnable() { @Override
public void run() {
sum++;
// Check if BT connection was established.
if (!BTIsConnected) {
return;
} try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
BTIsConnected = false;
setTitle("Not connected");
} // Send msg start symbol
String messageStart = "S";
byte[] msgBufferStart = messageStart.getBytes(); try {
outStream.write(msgBufferStart);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
} // send msg
String message = Integer.toString((int) (speed));
// String message=et1.getText().toString();
byte[] msgBuffer = message.getBytes(); try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
} // Send msg end symbol
String messageEnd = "E";
byte[] msgBufferEnd = messageEnd.getBytes(); try {
outStream.write(msgBufferEnd);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
}
Message msg = msgHandler.obtainMessage();
if ((android.os.Build.VERSION.SDK_INT < NEED_BT_RECONNECT_DEVICE_MAX_SDK)
&& (sum > BT_RECONNECT_LIMIT_TIME)) {
msg.what = 0;
msgHandler.sendMessage(msg); // 又一次連線以確保藍芽通道連線順暢,不重連可能會當機
} Log.e(TAG, "sendThread: " + sum + " times.");
}
}; Runnable SendBTTestMsgThread = new Runnable() { @Override
public void run() {
sum++; // Check if BT connection was established.
if (!BTIsConnected) {
return;
} try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
BTIsConnected = false;
setTitle("Not connected");
} // Send msg start symbol
String messageStart = "S";
byte[] msgBufferStart = messageStart.getBytes(); try {
outStream.write(msgBufferStart);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
} // send msg
String message = et1.getText().toString();
byte[] msgBuffer = message.getBytes(); try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
} // Send msg end symbol
String messageEnd = "E";
byte[] msgBufferEnd = messageEnd.getBytes(); try {
outStream.write(msgBufferEnd);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
BTIsConnected = false;
setTitle("Not connected");
}
// 下面為循環發送訊息测試。若仅仅發送一次則不须要
// Message msg = msgHandler.obtainMessage();
//
// if ((android.os.Build.VERSION.SDK_INT <
// NEED_BT_RECONNECT_DEVICE_MAX_SDK)
// && sum > BT_RECONNECT_LIMIT_TIME) {
// msg.what = 0;
// msgHandler.sendMessage(msg); // 又一次連線以確保藍芽通道連線順暢,不重連可能會當機
// } else {
// msg.what = 1;
// msgHandler.sendMessage(msg);
// }
// Log.e(TAG, "sendThread: " + sum + " times.");
}
}; Runnable HudThread = new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
initControl();
}
}; private void initControl() {
// GPS啟動
locationManager = (LocationManager) this.getSystemService(SERVERNAME);
if (getGpsStates()) { getLocation();
} else {
Toast.makeText(this,
"Please enable your GPS, and re-run this program. ",
Toast.LENGTH_LONG).show();
btnStartHud.setEnabled(true);
hudThreadHandler.removeCallbacks(HudThread);
}
} // 打開GPS
public boolean getGpsStates() { if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
return true;
} else
return false;
} private void getLocation() {
try {
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_LOW); // 低功耗 // String provider = locationManager.getBestProvider(criteria,
// true); // 獲取GPS信息 // 設置監聽器,自動更新的最小時間為間隔(N*1000)秒或最小位移變化超過N米
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1 * 1000, (float) 0.5,
locationListener);
getSpeedThreadHandler.post(GetSpeedThread); // 改用GetSpeedThread來得到速度 locationManager.addGpsStatusListener(gpsstatusListener); } catch (Exception e) { }
} // 移動判定線程
Runnable GetSpeedThread = new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
if (speed == lastSpeed) { //若和五秒前的速度一樣,則視為靜止。速度為零
speed = 0;
tvGPSInfo.setText("GPS Info" + "\n" + " 速度 = " + speed
+ "km/h" + "\n Thread Sum = " + sum);
if (BTIsConnected) {
sendBTMsgHandler.post(SendBTMsgThread);
} else {
setTitle("Not connected");
}
} else {
lastSpeed = speed;
}
getSpeedThreadHandler.postDelayed(GetSpeedThread, STATIC_CONTINUED_TIME);
} }; private final LocationListener locationListener = new LocationListener() { public void onLocationChanged(Location location) {
speed = (float) (location.getSpeed() * 3.6);// 速度
if (speed < 3.0) { // 速度修正,若時速在3km/h內則視為0km/h。因為要位移0.85m以上,時速才會高於3.06km/h。0.85m相當於沒有動,視為是定位的誤差
speed = 0;
}
tvGPSInfo.setText("GPS Info" + "\n" + " 速度 = " + speed + "km/h"
+ "\n Thread Sum = " + sum);
if (BTIsConnected) {
sendBTMsgHandler.post(SendBTMsgThread);
} else {
setTitle("Not connected");
} } public void onProviderDisabled(String provider) {
// WriteDB(null);
} public void onProviderEnabled(String provider) {
} public void onStatusChanged(String provider, int status, Bundle extras) {
// "<locationListener>本地監聽狀態改變\n");
switch (status) { case LocationProvider.AVAILABLE:
// "<locationListener>有可用衛星\n");
break;
case LocationProvider.OUT_OF_SERVICE:
// "<locationListener>無可用衛星服務\n");
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
// "<locationListener>暫時無可用衛星服務\n");
break;
} } }; private final GpsStatus.Listener gpsstatusListener = new GpsStatus.Listener() {
private GpsStatus gpsStatus; public void onGpsStatusChanged(int event) { gpsStatus = locationManager.getGpsStatus(null); switch (event) {
case GpsStatus.GPS_EVENT_FIRST_FIX:
// GPS時間
gpdDate = new Date(gpsStatus.getTimeToFirstFix());
break; case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
// 得到全部收到的衛星的信息,包含 衛星的高度角、方位角、信噪比、和偽隨機號(衛星編號)
Iterable<GpsSatellite> allSatellites;
allSatellites = gpsStatus.getSatellites(); tempBuilder.delete(0, tempBuilder.length());
for (GpsSatellite gpsstate : allSatellites) {
tempBuilder.append("方位角 " + gpsstate.getAzimuth());
tempBuilder.append("\n");
tempBuilder.append("高度角 " + gpsstate.getElevation());
tempBuilder.append("\n");
tempBuilder.append(" 偽隨機號(衛星編號)" + gpsstate.getPrn());
tempBuilder.append("\n");
tempBuilder.append(" 信噪比 " + gpsstate.getSnr());
tempBuilder.append("\n");
} int maxSatellites = gpsStatus.getMaxSatellites(); Iterator<GpsSatellite> it = gpsStatus.getSatellites()
.iterator(); int satellite = 0; while (it.hasNext() && satellite <= maxSatellites) { // GpsSatellite s = it.next();
it.next();
satellite++; } // 計算衛星個數,可在此打印出衛星的其他信息 satelliteCount = satellite;
tempBuilder.append("當前衛星個數:" + satellite + "\n");
tvGPSState.setText("GPS State" + "\n" + " 當前衛星個數:"
+ satellite);
break; case GpsStatus.GPS_EVENT_STARTED:
// Event sent when the GPS system has started. // "<gpsstatusListener>开始信號GPS_EVENT_STARTED\n");
break; case GpsStatus.GPS_EVENT_STOPPED:
// Event sent when the GPS system has stopped.
// "<gpsstatusListener>結束信號GPS_EVENT_STOPPED\n");
break; default:
// "<gpsstatusListener>沒有獲取到狀態信息\n");
break;
}
}
}; @Override
public void onStart() {
super.onStart();
if (D)
Log.e(TAG, "++ ON START ++");
} @Override
public void onResume() {
super.onResume(); if (D) {
Log.e(TAG, "+ ON RESUME +");
Log.e(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");
}
} @Override
public void onPause() {
super.onPause(); if (D)
Log.e(TAG, "- ON PAUSE -"); } @Override
public void onStop() {
super.onStop();
if (D)
Log.e(TAG, "-- ON STOP --");
} @Override
public void onDestroy() {
super.onDestroy();
if (D)
Log.e(TAG, "--- ON DESTROY ---"); if (outStream != null) {
try {
outStream.flush();
} catch (IOException e) {
Log.e(TAG, "ON PAUSE: Couldn't flush output stream.", e);
}
} if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
Log.e(TAG, "Cancel discovery");
} try {
if (BTIsConnected) {
btSocket.close();
BTIsConnected = false;
} } catch (IOException e2) {
Log.e(TAG, "ON PAUSE: Unable to close socket.", e2);
} if (ReceiverIsRegisted) {
unregisterReceiver(mReceiver);
} removeListener();
} private void removeListener() {
try {
locationManager.removeUpdates(locationListener);
locationManager.removeGpsStatusListener(gpsstatusListener);
} catch (Exception e) { }
} @Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
// TODO Auto-generated method stub
return super.onMenuItemSelected(featureId, item);
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
return super.onCreateOptionsMenu(menu);
} }

Android 行动装置端主介面Layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".HelmetHud" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" > <Button
android:id="@+id/btnDiscovery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="Discovery" /> <Button
android:id="@+id/btnConnect"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="Connect" /> </LinearLayout> <Spinner
android:id="@+id/spinner1"
android:layout_width="match_parent"
android:layout_height="wrap_content" /> <Button
android:id="@+id/btnStartHud"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:text="Start HUD" /> <TextView
android:id="@+id/tvGPSState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GPS State"
android:textAppearance="?android:attr/textAppearanceLarge" /> <TextView
android:id="@+id/tvGPSInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GPS Info"
android:textAppearance="? android:attr/textAppearanceLarge" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp" > <EditText
android:id="@+id/editText1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:ems="10" > <requestFocus />
</EditText> <Button
android:id="@+id/btnSend"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:text="SendTestMSG" /> </LinearLayout> </LinearLayout>

安全帽端设备Arduino主程序

#define CMD_MAXNUM 5
#define LED_DIGIT_NUM 8 int DIO_Pin = 8; //pin 14 on the 75HC595(DIO)
int RCK_Pin = 9; //pin 12 on the 75HC595(RCLK)
int SCK_Pin = 10; //pin 11 on the 75HC595(SCLK) // 蜂鳴器變量
int melody = 523;
int duration = 500; // 500 miliseconds unsigned char LED_0F[] =
{// 0 1 2 3 4 5 6 7 8 9
0xC0,0xCF,0x92,0x86,0x8D,0xA4,0xA0,0xCE,0x80,0x84
}; //顛倒後的字元
// 全局變量
unsigned char SEG[LED_DIGIT_NUM]; //用於LED的4位顯示緩存
char cmd[CMD_MAXNUM]; //藍芽接收的字串(車速) void SEG_OUT(unsigned char X)
{
unsigned char i;
for(i=LED_DIGIT_NUM;i>=1;i--)
{
if (X&0x80)
digitalWrite(DIO_Pin, HIGH);
else
digitalWrite(DIO_Pin, LOW);
X<<=1;
digitalWrite(SCK_Pin, LOW);
digitalWrite(SCK_Pin, HIGH);
}
} void DisplaySegment(void)
{
unsigned char led_table; // 查表指針
unsigned char i;
//顯示第1位
if(SEG[0]!=0)
{
led_table = LED_0F[SEG[0]];
i = led_table; SEG_OUT(i);
SEG_OUT(0x01); digitalWrite(RCK_Pin, LOW);
digitalWrite(RCK_Pin, HIGH);
}
//顯示第2位
if(SEG[0]!=0 || SEG[1]!=0)
{
led_table = LED_0F[SEG[1]];
i = led_table; SEG_OUT(i);
SEG_OUT(0x02); digitalWrite(RCK_Pin, LOW);
digitalWrite(RCK_Pin, HIGH);
} //顯示第3位
if(SEG[0]!=0 || SEG[1]!=0 || SEG[2]!=0)
{
led_table = LED_0F[SEG[2]];
i = led_table; SEG_OUT(i);
SEG_OUT(0x04); digitalWrite(RCK_Pin, LOW);
digitalWrite(RCK_Pin, HIGH);
} //顯示第4位 led_table = LED_0F[SEG[3]];
i = led_table; SEG_OUT(i);
SEG_OUT(0x08); digitalWrite(RCK_Pin, LOW);
digitalWrite(RCK_Pin, HIGH);
} // 設定整個四合一型七段顯示器想要顯示的數字
// 参數number的範圍應是0~9999
void setNumber(int number)
{
int n0, n1, n2, n3;
n3 = number / 1000;
number %= 1000;
n2 = number / 100;
number %= 100;
n1 = number / 10;
n0 = number % 10; // 每位數值清零
SEG[0]=0;
SEG[1]=0;
SEG[2]=0;
SEG[3]=0;
// 求出每個位數的值後,分別更新
// 因為要實現反射式HUD,所以位置要顛倒
SEG[0]=n3; //第一位字元
SEG[1]=n2; //第二位字元
SEG[2]=n1; //第三位字元
SEG[3]=n0; //第四位字元 DisplaySegment();
} void setup()
{
Serial.begin(9600); // 藍牙模組預設baud rate = 9600
pinMode(DIO_Pin, OUTPUT);
pinMode(RCK_Pin, OUTPUT);
pinMode(SCK_Pin, OUTPUT);
} void loop()
{ while (Serial.available()) //connected
{
memset(cmd, 0, CMD_MAXNUM);
if(Serial.read() == 'S')
{
for(int i=0;i<CMD_MAXNUM;i++)
{
cmd[i] = Serial.read();
if(cmd[i]=='E')
{
cmd[i]=0;
break;
}
else if(cmd[i]<48 || cmd[i]>57)
{
cmd[i]=0;
i--;
continue;
}
}
cmd[CMD_MAXNUM-1]='\0';
Serial.print(cmd);
Serial.print("\n");
}
} setNumber(atoi(cmd));
if(atoi(cmd)>=70)
{
// 在 pin7 上輸出聲音,每個音階響 1 秒
tone(7, melody, duration); }
}