android蓝牙与下位机交互

10 篇文章 0 订阅

最近在做一个项目,是蓝牙与下位机进行交互,只是简单的数据交互,使用蓝牙4.0.。。。

先看看项目的简易通信协议:

     协议内容,是一个44位字节的通信命令式协议:

协议格式:

   输出格式:pump on + (char)13 + (char)10

    返回格式 10PID 9PStatus 10FID 9FStatus <CF><LR>,去掉结尾符共42位,结尾以char[10]char[13];

解释下:

char[13]char[10]就是“\r\n”;



看懂了这些,开始找思路,其实蓝牙无非就是socket通信,下位机当做S端,终端设备是C端,请求连接S端,然后用socket向S端写数据,写过C的都知道,C中没有String的概

念,只有char*[],这样就有个问题,我们java的命令行是拼接的String字符串,如何去转化成char*[]呢,我们知道C接受数据时候的没有byte,但是要理解byte其实就是无属的char

即(C没有这个类型,byte表示一个字节,对应C的unsigned char),java是没有指针概念的,,其实这里非常简单,,char*[]无非就是一个char型的数组,那么有了这条思路,我

们用String.getByteArray()

即可。转化成字节数组,那么对应的就是C中unsigned char[ ],既然数据类型匹配完成,那么就开始从java入手书写代码:

主界面:


<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <ImageButton
            android:id="@+id/btnOpenBT"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@drawable/bluetooth_off" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >

            <TextView
                android:id="@+id/tvDeviceName"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:gravity="right"
                android:text="Bluetooth"
                android:textSize="14sp" />
        </LinearLayout>

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            android:text="写入测试" />

        <EditText
            android:id="@+id/output_test"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="数据回显测试" />

        <TextView
            android:id="@+id/input_test"
            android:layout_width="fill_parent"
            android:textSize="14sp"
            android:layout_height="30dp" />

        <Button
            android:id="@+id/start_test"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="开始测试" />

        <Button
            android:id="@+id/btn1"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="pump on" />

        <Button
            android:id="@+id/btn2"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="pump off" />

        <Button
            android:id="@+id/btn3"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="detector pid_fid" />

        <Button
            android:id="@+id/btn4"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="ignite" />
    </LinearLayout>

</ScrollView>
搜索蓝牙布局界面:

<?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:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/newdevice" />

    <ListView
        android:id="@+id/lvNewDevices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:stackFromBottom="true" >
    </ListView>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/paireddevice" />

    <ListView
        android:id="@+id/lvPairedDevices"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:stackFromBottom="true" >
    </ListView>

    <Button
        android:id="@+id/btnScan"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/scan" />

</LinearLayout>

开始代码:

package com.simpledraw.bluetoothservice;

import java.text.DecimalFormat;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;

import com.simpledraw.bluetooth.BluetoothCommService;
import com.simpledraw.bluetooth.BluetoothEnvironment;
import com.simpledraw.bluetooth.ConstantsBluetooth;
import com.simpledraw.bluetooth.ScanBTDeviceActivity;
import com.simpledraw.bluetooth.data.Datagram;

public class MainActivity extends Activity {

	private TextView tvDirection1, tvDirection2;
	private ScaleAnimation animTemperature, animHumidity;
	private RotateAnimation animPressure;
	private float currentDirection1 = 0, currentDirection2 = 0, currentTemperature = ConstantsEwins.MIN_TEMPERATURE_Y,
			currentHumidity = 0.0f, currentPressure = 0;
	DecimalFormat df = new DecimalFormat("0.00");

	private ImageButton btnOpenBT = null;
	private TextView tvDeviceName = null;

	private Datagram datagram = new Datagram();
	private BluetoothAdapter btAdapter;
	private BluetoothDevice mDevice;

	private int[] checked = new int[11];

	private long exitTime = 0;// 用于再按一次退出
	
	private String executStr = "";
	
	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		getBluetoothAdapter();

		findAllControls();

	}
	
	private EditText output_edit;
	private TextView input_edit;
	
	private Button startTest;
	
	private void findAllControls() {
		
		
		output_edit = (EditText) findViewById(R.id.output_test);
		input_edit = (TextView) findViewById(R.id.input_test);
		
		startTest = (Button) findViewById(R.id.start_test);
		
		((Button)findViewById(R.id.btn1)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				executStr = ((Button)v).getText().toString();
				output_edit.setText("");
				executStr+="\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
				output_edit.setText(executStr);
			}
		});
		((Button)findViewById(R.id.btn2)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				executStr = ((Button)v).getText().toString();
				output_edit.setText(executStr);
				output_edit.setText("");
				executStr+="\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
			}
		});
		((Button)findViewById(R.id.btn3)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				executStr = ((Button)v).getText().toString();
				output_edit.setText(executStr);
				output_edit.setText("");
				executStr="log start\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
				executStr="detector pid_fid\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
			}
		});
		((Button)findViewById(R.id.btn4)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				executStr = ((Button)v).getText().toString();
				output_edit.setText(executStr);
				output_edit.setText("");
				executStr+="\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
				
			}
		});
		
		startTest.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				if(BluetoothEnvironment.service!=null){
					output_edit.setText("");
//					String outputData = output_edit.getText().toString();
					executStr+="\r\n";
//					byte[] bytes = outputData.getBytes();
//					byte[] bytes2 = new byte[bytes.length+2];
//					for(int i = 0;i<bytes.length;i++){
//						bytes2[i] = bytes[i];
//					}
//					bytes2[bytes2.length-2] = 13;
//					bytes2[bytes2.length-1] = 10;
					BluetoothEnvironment.service.write(executStr.getBytes());
				}
			}
		});
		
		

		btnOpenBT = (ImageButton) findViewById(R.id.btnOpenBT);
		btnOpenBT.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				Intent intent = new Intent(MainActivity.this, ScanBTDeviceActivity.class);
				startActivityForResult(intent, ConstantsBluetooth.REQUEST_CONNECT_DEVICE);
			}
		});
		tvDeviceName = (TextView) findViewById(R.id.tvDeviceName);

		for (int i = 0; i < 11; i++)
			checked[i] = View.VISIBLE;
	}


	private void getBluetoothAdapter() {
		btAdapter = BluetoothAdapter.getDefaultAdapter();
		if (btAdapter == null) {
			Toast.makeText(MainActivity.this, "本设备不支持蓝牙", Toast.LENGTH_SHORT).show();
			finish();
			return;
		}
		while (!btAdapter.isEnabled()) {
			if (btAdapter.enable()) {
				Toast.makeText(MainActivity.this, "蓝牙已经启动", Toast.LENGTH_SHORT).show();
				break;
			} else {
				Toast.makeText(MainActivity.this, "蓝牙启动失败", Toast.LENGTH_SHORT).show();
				return;
			}
		}
		if (btAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
			Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
			intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
			startActivity(intent);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		menu.add(0, 1, 0, R.string.tianjiachuanganqi);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if (item.getOrder() == 0) {
			Intent intent = new Intent(MainActivity.this, AddDeleteSensorsActivity.class);
			intent.putExtra("checked", checked);
			startActivityForResult(intent, ConstantsEwins.REQUEST_ADDDELETE_SENSORS);
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	protected void onResume() {
		System.out.println("------- ON RESUME ---------");
		super.onResume();
		if (BluetoothEnvironment.service != null) {
			if (btAdapter.isEnabled() && BluetoothEnvironment.service.getState() == ConstantsBluetooth.STATE_NONE) {
				BluetoothEnvironment.service.start();
			}
			BluetoothEnvironment.service.setHandler(mHandler, null);
		} else {
			BluetoothEnvironment.service = new BluetoothCommService(this, mHandler, null);
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		switch (requestCode) {
		case ConstantsBluetooth.REQUEST_CONNECT_DEVICE:
			if (resultCode == RESULT_OK) {
				String address = data.getExtras().getString(ConstantsBluetooth.DEVICE_ADDRESS);
				mDevice = btAdapter.getRemoteDevice(address);
				if (mDevice == null)
					return;
				if (BluetoothEnvironment.service != null)
					BluetoothEnvironment.service.connect(mDevice);
			}
			break;
		case ConstantsEwins.REQUEST_ADDDELETE_SENSORS:
			if (resultCode == RESULT_OK) {
			}
			break;
		default:
			break;
		}
	}

	@SuppressLint("HandlerLeak")
	private final Handler mHandler = new Handler() {
		int length = 0;
		boolean firstReceived = true;
		long lastReadTime = System.currentTimeMillis();
		StringBuffer sb = new StringBuffer(ConstantsEwins.DATA_LENGTH);

		@Override
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case ConstantsBluetooth.MESSAGE_STATE_CHANGE:
				switch (msg.arg1) {
				case ConstantsBluetooth.STATE_CONNECTED:
					Toast.makeText(MainActivity.this, "已连接至" + msg.obj + "!!fdasfds", Toast.LENGTH_SHORT).show();
					tvDeviceName.setText((String) msg.obj);
					btnOpenBT.setBackgroundResource(R.drawable.bluetooth_on);
					break;
				case ConstantsBluetooth.STATE_CONNECTING:
					Toast.makeText(MainActivity.this, "正在连接,请稍候", Toast.LENGTH_LONG).show();
					break;
				case ConstantsBluetooth.STATE_LISTEN:
					if (ConstantsBluetooth.CONNECTION_FAILED.equals(msg.obj)) {
						Toast.makeText(MainActivity.this, "连接不成功", Toast.LENGTH_SHORT).show();
						btnOpenBT.setBackgroundResource(R.drawable.bluetooth_off);
					} else if (ConstantsBluetooth.CONNECTION_LOST.equals(msg.obj)) {
						Toast.makeText(MainActivity.this, "连接丢失", Toast.LENGTH_SHORT).show();
						btnOpenBT.setBackgroundResource(R.drawable.bluetooth_off);
					}
					break;
				case ConstantsBluetooth.STATE_NONE:
					btnOpenBT.setBackgroundResource(R.drawable.bluetooth_off);
					break;
				}
				break;
			case ConstantsBluetooth.MESSAGE_WRITE:
				break;
			case ConstantsBluetooth.MESSAGE_READ:
				if (firstReceived)
					lastReadTime = System.currentTimeMillis();
				firstReceived = false;

				if (System.currentTimeMillis() - lastReadTime > ConstantsEwins.DATAGRAM_INTERVAL) {
					if (length == ConstantsEwins.DATAGRAM_BYTES_LENGTH) {
						datagram.parseDatagram(sb.toString());
						modifyViews();
					}
					sb = new StringBuffer(ConstantsEwins.DATA_LENGTH);
					length = 0;
				}

				String data = (String) msg.obj;
				System.out.println("接收到的数据:" + data);
				Log.e("texts",data);
				length += msg.arg1;
				sb.append(data);
				lastReadTime = System.currentTimeMillis();
				input_edit.setText(data);//sb.toString()
				break;
			}
		}
	};

	@SuppressLint("NewApi")
	private void modifyViews() {
		tvDirection1.setText("风向1: " + (datagram.getDirection1() - (datagram.getDirection1() / 360) * 360) + "°");
		tvDirection2.setText("风向2: " + (datagram.getDirection2() - (datagram.getDirection2() / 360) * 360) + "°");

		// 风速改变
		long newDuration;
		newDuration = calcSpeedDuration(datagram.getSpeed1());
		newDuration = calcSpeedDuration(datagram.getSpeed2());
		newDuration = calcSpeedDuration(datagram.getSpeed3());
		newDuration = calcSpeedDuration(datagram.getSpeed4());
		newDuration = calcSpeedDuration(datagram.getSpeed5());
		newDuration = calcSpeedDuration(datagram.getSpeed6());
		
		currentDirection1 = datagram.getDirection1();
		currentDirection2 = datagram.getDirection2();

		// 温度、湿度和气压
		float newTemperature = (datagram.getTemperature() - ConstantsEwins.MIN_TEMPERATURE_VALUE)
				* ConstantsEwins.TEMPERATURE_STEP + ConstantsEwins.MIN_TEMPERATURE_Y;
		float newHumidity = datagram.getHumidity() * ConstantsEwins.HUMIDITY_STEP + ConstantsEwins.MIN_HUMIDITY_Y;
		float newPressure = (datagram.getPressure() - ConstantsEwins.MIN_PRESSURE_VALUE) * ConstantsEwins.PRESSURE_STEP;

		animTemperature = new ScaleAnimation(1.0f, 1.0f, currentTemperature, newTemperature,
				Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0.8f);
		animTemperature.setDuration(500);
		animTemperature.setFillAfter(true);

		animHumidity = new ScaleAnimation(1.0f, 1.0f, currentHumidity, newHumidity, Animation.RELATIVE_TO_SELF, 0,
				Animation.RELATIVE_TO_SELF, 0.8f);
		animHumidity.setDuration(500);
		animHumidity.setFillAfter(true);

		animPressure = new RotateAnimation(currentPressure, newPressure, Animation.RELATIVE_TO_SELF, 0.5f,
				Animation.RELATIVE_TO_SELF, 0.5f);
		animPressure.setDuration(500);
		animPressure.setFillAfter(true);
		
		currentTemperature = newTemperature;
		currentHumidity = newHumidity;
		currentPressure = newPressure;

	}

	private long calcSpeedDuration(float speed) {
		int level;
		if (speed < 1.5f)
			level = 1;
		else if (speed > 1.6 && speed < 5.4)
			level = 2;
		else if (speed > 5.5 && speed < 7.9)
			level = 3;
		else if (speed > 8.0 && speed < 10.7)
			level = 4;
		else if (speed > 10.8 && speed < 13.8)
			level = 5;
		else if (speed > 13.9 && speed < 17.1)
			level = 6;
		else if (speed > 17.2 && speed < 20.7)
			level = 7;
		else if (speed > 20.8 && speed < 24.4)
			level = 8;
		else if (speed > 24.5 && speed < 28.4)
			level = 9;
		else if (speed > 28.5 && speed < 32.6)
			level = 10;
		else
			level = 11;

		long duration;
		duration = (11 - level) * ConstantsEwins.SPEED_DURATION_STEP + ConstantsEwins.MIN_SPEED_DURATION;
		return duration;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
			if ((System.currentTimeMillis() - exitTime) > 2000) {
				Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
				exitTime = System.currentTimeMillis();
			} else {
				finish();
				System.exit(0);
			}
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}

}

package com.simpledraw.bluetoothservice;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class DirectionPointerView extends ImageView {

	private float mDirection;
	private Drawable compass;

	public DirectionPointerView(Context context) {
		super(context);
		mDirection = 0.0f;
		compass = null;
	}

	public DirectionPointerView(Context context, AttributeSet attrs) {
		super(context, attrs);
		mDirection = 0.0f;
		compass = null;
	}

	public DirectionPointerView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		mDirection = 0.0f;
		compass = null;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (compass == null) {
			compass = getDrawable();
			compass.setBounds(0, 0, getWidth(), getHeight());
		}

		canvas.save();
		canvas.rotate(mDirection, getWidth() / 2, getHeight() / 2);
		compass.draw(canvas);
		canvas.restore();
	}

	public void updateDirection(float direction) {
		mDirection = direction;
		invalidate();
	}

}

package com.simpledraw.bluetoothservice;

public class ConstantsEwins {

	public static final int REQUEST_ADDDELETE_SENSORS = 2;

	public static final float MAX_SPEED_VALUE = 100;
	public static final float MIN_SPEED_VALUE = 0;
	public static final int MIN_SPEED_DURATION = 500;
	public static final int SPEED_DURATION_STEP = 500;

	public static final float MAX_TEMPERATURE_VALUE = 53;
	public static final float MIN_TEMPERATURE_VALUE = -40;
	public static final float MIN_TEMPERATURE_Y = 0.02f;
	public static final float TEMPERATURE_STEP = 0.01185f;

	public static final float MIN_HUMIDITY_Y = 0.0f;
	public static final float HUMIDITY_STEP = 0.01118f;
	public static final float MAX_PRESSURE_VALUE = 120;
	public static final float MIN_PRESSURE_VALUE = 10;
	public static final float PRESSURE_STEP = 2.38f;
	
	public static final int DATAGRAM_INTERVAL = 500;
	// 报文的字节数
	public static final int DATAGRAM_BYTES_LENGTH = 20;
	// 需要使用的数据长度,例如将字节变成16进制字符串的长度,0x80变成80
	public static final int DATA_LENGTH = 40;

}

package com.simpledraw.bluetooth.data;

import android.annotation.SuppressLint;

@SuppressLint("DefaultLocale")
public class DataTypeConverter {

	public static String double2Str(double d) {
		return String.format("%.1f", d);
	}

	public static double str2Double(String str) {
		return Double.parseDouble(str);
	}

	public static String getMacAddress(String pStr) {
		return pStr.substring(pStr.length() - 17);
	}

	public static double hexStr2Double(String pStr) {
		int i = Integer.parseInt(pStr, 16);
		double d;
		if (pStr.startsWith("F")) {
			d = (double) (i - 65536) / 100;
		} else {
			d = (double) i / 100;
		}
		d = ((double) Math.round(d * 10)) / 10;
		return d;
	}

	public static float hexStr2Float(String pStr) {
		return (float) (hexStr2Double(pStr));
	}

	public static int hexStr2Int(String pStr) {
		return Integer.parseInt(pStr, 16);
	}
}


package com.simpledraw.bluetooth.data;

import java.text.DecimalFormat;

import com.simpledraw.bluetoothservice.ConstantsEwins;


public class Datagram {
	private float speed1;
	private float speed2;
	private float speed3;
	private float speed4;
	private float speed5;
	private float speed6;

	private int direction1;
	private int direction2;

	private float temperature;
	private int humidity;
	private float pressure;
	DecimalFormat df = new DecimalFormat(".00");

	public float getSpeed1() {
		return speed1;
	}

	public void setSpeed1(float speed1) {
		this.speed1 = speed1 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed1 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed1);
	}

	public float getSpeed2() {
		return speed2;
	}

	public void setSpeed2(float speed2) {
		this.speed2 = speed2 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed2 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed2);
	}

	public float getSpeed3() {
		return speed3;
	}

	public void setSpeed3(float speed3) {
		this.speed3 = speed3 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed3 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed3);
	}

	public float getSpeed4() {
		return speed4;
	}

	public void setSpeed4(float speed4) {
		this.speed4 = speed4 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed4 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed4);
	}

	public float getSpeed5() {
		return speed5;
	}

	public void setSpeed5(float speed5) {
		this.speed5 = speed5 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed5 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed5);
	}

	public float getSpeed6() {
		return speed6;
	}

	public void setSpeed6(float speed6) {
		this.speed6 = speed6 > ConstantsEwins.MAX_SPEED_VALUE ? ConstantsEwins.MAX_SPEED_VALUE
				: (speed6 < ConstantsEwins.MIN_SPEED_VALUE ? ConstantsEwins.MIN_SPEED_VALUE : speed6);
	}

	public int getDirection1() {
		return direction1;
	}

	public void setDirection1(int direction1) {
		this.direction1 = direction1;
	}

	public int getDirection2() {
		return direction2;
	}

	public void setDirection2(int direction2) {
		this.direction2 = direction2;
	}

	public float getTemperature() {
		return temperature;
	}

	public void setTemperature(float temperature) {
		this.temperature = temperature > ConstantsEwins.MAX_TEMPERATURE_VALUE ? ConstantsEwins.MAX_TEMPERATURE_VALUE
				: (temperature < ConstantsEwins.MIN_TEMPERATURE_VALUE ? ConstantsEwins.MIN_TEMPERATURE_VALUE
						: temperature);
	}

	public int getHumidity() {
		return humidity;
	}

	public void setHumidity(int humidity) {
		this.humidity = humidity > 100 ? 100 : (humidity < 0 ? 0 : humidity);
	}

	public float getPressure() {
		return pressure;
	}

	public void setPressure(float pressure) {
		this.pressure = pressure > ConstantsEwins.MAX_PRESSURE_VALUE ? ConstantsEwins.MAX_PRESSURE_VALUE
				: (pressure < ConstantsEwins.MIN_PRESSURE_VALUE ? ConstantsEwins.MIN_PRESSURE_VALUE : pressure);
	}

	public void parseDatagram(String datagram) {
		// datagram = "01076954204231804B0A08072102C3046307F209";
		if (datagram == null || datagram == "") {
			System.out.println("kong");
			return;
		}
		setSpeed1(calcSpeed(datagram.substring(4, 6)));
		setSpeed2(calcSpeed(datagram.substring(6, 8)));
		setSpeed3(calcSpeed(datagram.substring(8, 10)));
		setSpeed4(calcSpeed(datagram.substring(10, 12)));
		setSpeed5(calcSpeed(datagram.substring(12, 14)));
		setSpeed6(calcSpeed(datagram.substring(14, 16)));

		setDirection1(calcDirection(datagram.substring(22, 24) + datagram.substring(20, 22)));
		setDirection2(calcDirection(datagram.substring(26, 28) + datagram.substring(24, 26)));

		setTemperature(calcTemperature(datagram.substring(30, 32) + datagram.substring(28, 30)));
		setHumidity(calcHumidity(datagram.substring(34, 36) + datagram.substring(32, 34)));
		setPressure(calcPressure(datagram.substring(38, 40) + datagram.substring(36, 38)));
	}

	private float calcSpeed(String pStrSpeedHex) {
		float speed = DataTypeConverter.hexStr2Int(pStrSpeedHex);
		return speed * 0.765f + 0.35f;
	}

	private int calcDirection(String pStrDirectionHex) {
		int direction = DataTypeConverter.hexStr2Int(pStrDirectionHex);
		return (int) (direction * 0.094230277);
	}

	private float calcTemperature(String pStrTempHex) {
		float temperature = DataTypeConverter.hexStr2Int(pStrTempHex);
		return (float) (temperature * 0.088695557 - 86.38);
	}

	private int calcHumidity(String pStrHumidityHex) {
		int humidity = DataTypeConverter.hexStr2Int(pStrHumidityHex);
		return (int) (humidity * 0.031933594);
	}

	private float calcPressure(String pStrPressureHex) {
		float pressure = DataTypeConverter.hexStr2Int(pStrPressureHex);
		return (float) (pressure * 0.03479165 + 10.55);
	}
}

package com.simpledraw.bluetooth;

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.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
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 com.simpledraw.bluetooth.data.DataTypeConverter;
import com.simpledraw.bluetoothservice.R;


public class ScanBTDeviceActivity extends Activity {

	private Button btnScan = null;
	private ListView lvNewDevices, lvPairedDevices;
	private ArrayAdapter<String> adapterNewDevices, adapterPairedDevice;

	private BluetoothAdapter btAdapter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		// Setup the window
		requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
		setContentView(R.layout.device_list);

		findAllViews();

		btAdapter = BluetoothAdapter.getDefaultAdapter();
		if (btAdapter == null) {
			finish();
			return;
		}
		Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
		for (BluetoothDevice device : pairedDevices) {
			adapterPairedDevice.add(device.getName() + "\n" + device.getAddress());
		}

		IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		registerReceiver(mReceiver, filter);
	}

	private void findAllViews() {
		btnScan = (Button) findViewById(R.id.btnScan);

		lvNewDevices = (ListView) findViewById(R.id.lvNewDevices);
		adapterNewDevices = new ArrayAdapter<String>(ScanBTDeviceActivity.this, android.R.layout.simple_list_item_1);
		lvNewDevices.setAdapter(adapterNewDevices);
		lvNewDevices.setOnItemClickListener(DeviceListClickListener);

		lvPairedDevices = (ListView) findViewById(R.id.lvPairedDevices);
		adapterPairedDevice = new ArrayAdapter<String>(ScanBTDeviceActivity.this, android.R.layout.simple_list_item_1);
		lvPairedDevices.setAdapter(adapterPairedDevice);
		lvPairedDevices.setOnItemClickListener(DeviceListClickListener);

		btnScan.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				discoverDevices();
			}
		});
	}

	private void discoverDevices() {
		btnScan.setEnabled(false);
		setTitle(R.string.scanning);
		if (btAdapter.isDiscovering()) {
			btAdapter.cancelDiscovery();
		}
		btAdapter.startDiscovery();
	}

	private OnItemClickListener DeviceListClickListener = new OnItemClickListener() {
		@Override
		public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
			btAdapter.cancelDiscovery();
			String address = DataTypeConverter.getMacAddress(((TextView) arg1).getText().toString());
			Intent intent = new Intent();
			intent.putExtra(ConstantsBluetooth.DEVICE_ADDRESS, address);
			setResult(RESULT_OK, intent);
			finish();
		}
	};

	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (btAdapter != null) {
			btAdapter.cancelDiscovery();
		}
		unregisterReceiver(mReceiver);
	};

	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)
					adapterNewDevices.add(device.getName() + "\n" + device.getAddress());
			} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
				btnScan.setEnabled(true);
				setTitle(R.string.select_device);
			}
		}
	};
}

package com.simpledraw.bluetooth;

import java.util.UUID;

public class ConstantsBluetooth {
	public static final int REQUEST_CONNECT_DEVICE = 1;
	
	public static final String DEVICE_ADDRESS = "deviceaddress";
	public static final String CONNECTED_DEVICE_NAME = "connecteddevicename";

	public static final String SERVER_NAME = "BluetoothComm";
	public static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

	public static final int MESSAGE_STATE_CHANGE = 1;
	public static final int MESSAGE_READ = 2;
	public static final int MESSAGE_WRITE = 3;
	public static final int MESSAGE_DEVICE_NAME = 4;

	public static final int STATE_NONE = 0; // do nothing
	public static final int STATE_LISTEN = 1; // 监听连接
	public static final int STATE_CONNECTING = 2; // now initiating an outgoing
	public static final int STATE_CONNECTED = 3; // 已连接上远程设备

	public static final String CONNECTION_FAILED = "fail";
	public static final String CONNECTION_LOST = "lost";
	public static final String CONNECTION_CONNECTED = "connected";

	public static final String COMMAND_1 = "uLPCC_0000CD?";
	public static final byte[] COMMAND_ACK = new byte[] { (byte) 0x06 };
}

package com.simpledraw.bluetooth;


public class BluetoothEnvironment {

	public static BluetoothCommService service;
}

package com.simpledraw.bluetooth;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.UUID;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

public class BluetoothCommService implements Serializable {

	private static final long serialVersionUID = 1L;
	private static final String NAME = "BluetoothComm";
	// SPP协议UUID
	private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
	// Member fields
	private final BluetoothAdapter mAdapter;
	private Handler mHandler, receiveHandler;
	private int mState;
	private AcceptThread mAcceptThread;
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;

	public BluetoothCommService(Context context, Handler handler, Handler p_receiveHandler) {
		mAdapter = BluetoothAdapter.getDefaultAdapter();
		mHandler = handler;
		receiveHandler = p_receiveHandler;
		mState = ConstantsBluetooth.STATE_NONE;
	}

	public void setHandler(Handler pHandler, Handler p_receiveHandler) {
		mHandler = pHandler;
		receiveHandler = p_receiveHandler;
	}

	/**
	 * Set the current state of the chat connection
	 * 
	 * @param state
	 *            An integer defining the current connection state
	 */
	private synchronized void setState(int state) {
		mState = state;
		mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	}

	/**
	 * Return the current connection state.
	 */
	public synchronized int getState() {
		return mState;
	}

	/**
	 * 开始服务,主要是开始AcceptThread线程,打开一个监听回话由BaowenActivity的onResume方法调用,
	 * ***开启监听线程****
	 */
	public synchronized void start() {
		// 取消所有正在尝试连接的线程
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// 取消所有已经连接上的线程
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// 打开一个监听BluetoothServerSocket的线程
		if (mAcceptThread == null) {
			mAcceptThread = new AcceptThread();
			mAcceptThread.start();// 开启监听线程
		}
		setState(ConstantsBluetooth.STATE_LISTEN);
	}

	/**
	 * Start the ConnectThread to initiate a connection to a remote device.
	 * 
	 * @param device
	 *            The BluetoothDevice to connect
	 */
	public synchronized void connect(BluetoothDevice device) {
		// Cancel any thread attempting to make a connection
		if (mState == ConstantsBluetooth.STATE_CONNECTING) {// 正在连接状态״̬
			if (mConnectThread != null) {// 有ConnectThread,结束
				mConnectThread.cancel();
				mConnectThread = null;
			}
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {// 有处于连接中的线程,结束
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Start the thread to connect with the given device
		mConnectThread = new ConnectThread(device);
		mConnectThread.start();// 开启新的连接请求线程
		setState(ConstantsBluetooth.STATE_CONNECTING);
	}

	/**
	 * Start the ConnectedThread to begin managing a Bluetooth connection
	 * 
	 * @param socket
	 *            The BluetoothSocket on which the connection was made
	 * @param device
	 *            The BluetoothDevice that has been connected
	 */
	public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
		// Cancel the thread that completed the connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Cancel the accept thread because we only want to connect to one
		// device
		if (mAcceptThread != null) {
			mAcceptThread.cancel();
			mAcceptThread = null;
		}

		// Start the thread to manage the connection and perform transmissions
		mConnectedThread = new ConnectedThread(socket);
		mConnectedThread.start();// 和客户端开始通信

		// 把已连接的设备的名称发给UI线程
		Message msg = mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_STATE_CHANGE);
		msg.arg1 = ConstantsBluetooth.STATE_CONNECTED;
		msg.obj = device.getName();
		mHandler.sendMessage(msg);

		mState = ConstantsBluetooth.STATE_CONNECTED;
	}

	/**
	 * Stop all threads
	 */
	public synchronized void stop() {
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
		if (mAcceptThread != null) {
			mAcceptThread.cancel();
			mAcceptThread = null;
		}
		setState(ConstantsBluetooth.STATE_NONE);
	}

	/**
	 * Write to the ConnectedThread in an unsynchronized manner
	 * 
	 * @param out
	 *            The bytes to write
	 * @see ConnectedThread#write(byte[])
	 */
	public void write(byte[] out) {
		// Create temporary object
		ConnectedThread r;
		// Synchronize a copy of the ConnectedThread
		synchronized (this) {
			if (mState != ConstantsBluetooth.STATE_CONNECTED)
				return;
			r = mConnectedThread;// 得到连接线程
		}
		// Perform the write unsynchronized
		r.write(out);
	}

	/**
	 * Indicate that the connection attempt failed and notify the UI Activity.
	 */
	private void connectionFailed() {
		setState(ConstantsBluetooth.STATE_LISTEN);

		// // Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_STATE_CHANGE);
		msg.obj = "fail";
		mHandler.sendMessage(msg);
	}

	/**
	 * Indicate that the connection was lost and notify the UI Activity.
	 */
	@SuppressWarnings("unused")
	private void connectionLost() {
		setState(ConstantsBluetooth.STATE_LISTEN);

		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_STATE_CHANGE);
		msg.obj = "lost";
		mHandler.sendMessage(msg);
	}

	/**
	 * This thread runs while listening for incoming connections. It behaves
	 * like a server-side client. It runs until a connection is accepted (or
	 * until cancelled). 监听准备进入的连接,类似于服务端,
	 */
	private class AcceptThread extends Thread {
		// 本地的socket
		private final BluetoothServerSocket mmServerSocket;

		public AcceptThread() {
			BluetoothServerSocket tmp = null;
			// 创建一个新的监听服务器socket
			try {
				// 根据名称,UUID创建并返回BluetoothServerSocket,这是创建BluetoothSocket服务器端的第一步
				tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, SPP_UUID);
			} catch (IOException e) {
			}
			// 得到BluetoothServerSocket对象
			mmServerSocket = tmp;
		}

		public void run() {
			setName("AcceptThread");// set the name of thread
			BluetoothSocket socket = null;

			// Listen to the server socket if we're not connected
			while (mState != ConstantsBluetooth.STATE_CONNECTED) {// 若没有连接,一直执行
				try {
					// 注意:BluetoothServerSocket的accept是一个阻塞性线程,一直到用户成功的配对,因此不要放在Activity中,
					// 远程蓝牙设备请求跟本设备建立连接请求时,
					// BluetoothServerSocket会在连接被接收时返回一个被连接的BluetoothSocket对象。
					socket = mmServerSocket.accept();
				} catch (IOException e) {
					break;
				}

				// 连接建立成功
				if (socket != null) {
					synchronized (BluetoothCommService.this) {
						switch (mState) {
						case ConstantsBluetooth.STATE_LISTEN:
						case ConstantsBluetooth.STATE_CONNECTING:
							// 已经连接上,开始ConnectedThread线程
							connected(socket, socket.getRemoteDevice());
							break;
						case ConstantsBluetooth.STATE_NONE:
						case ConstantsBluetooth.STATE_CONNECTED:
							// 或者设备没有准备好,或者已经连接,都将这个新的BluetoothSocket关掉
							// 释放服务套接字,但不会关闭accept()返回的BluetoothSocket
							try {
								socket.close();
							} catch (IOException e) {
							}
							break;
						}
					}
				}
			}
		}

		public void cancel() {
			try {
				mmServerSocket.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * This thread runs while attempting to make an outgoing connection with a
	 * device. It runs straight through; the connection either succeeds or
	 * fails. 监听准备出去的连接
	 */
	private class ConnectThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final BluetoothDevice mmDevice;// 远程设备

		public ConnectThread(BluetoothDevice device) {
			mmDevice = device;
			BluetoothSocket tmp = null;

			// Get a BluetoothSocket for a connection with the
			// given BluetoothDevice
			try {
				// 通过远程设备直接创建一个BluetoothSocket,要求该设备已经打开了BluetoothServerSocket获得一个BluetoothSocket对象
				tmp = device.createRfcommSocketToServiceRecord(SPP_UUID);
			} catch (IOException e) {
			}
			mmSocket = tmp;
		}

		public void run() {
			setName("ConnectThread");

			// 取消搜索
			mAdapter.cancelDiscovery();

			// Make a connection to the BluetoothSocket
			try {
				// This is a blocking call and will only return on a
				// successful connection or an exception
				mmSocket.connect();
			} catch (IOException e) {
				connectionFailed();
				// Close the socket
				try {
					mmSocket.close();
				} catch (IOException e2) {
				}
				// Start the service over to restart listening mode
				BluetoothCommService.this.start();
				return;
			}

			// Reset the ConnectThread because we're done
			synchronized (BluetoothCommService.this) {
				mConnectThread = null;
			}

			// Start the connected thread,已连接上,管理连接
			connected(mmSocket, mmDevice);
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
			}
		}
	}

	/**
	 * This thread runs during a connection with a remote device. It handles all
	 * incoming and outgoing transmissions. 监听
	 */
	private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final OutputStream mmOutStream;
		private final BufferedInputStream reader;

		public ConnectedThread(BluetoothSocket socket) {
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// Get the BluetoothSocket input and output streams
			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException e) {
			}

			reader = new BufferedInputStream(tmpIn);
			mmOutStream = tmpOut;
		}
		private String dataStr = "";
		@SuppressLint("NewApi")
		public void run() {
			byte[] buffer = new byte[44], temp = new byte[127];
			StringBuffer sb;
			String b;
			int numReadedBytes;
			boolean cleaning = true;
			long startTime = System.currentTimeMillis();

			// 保持对InputStream的监听
			while (true) {
				try {
//					if (cleaning) {
						if (System.currentTimeMillis() - startTime < 2000) {
//						
//						
//						
//							numReadedBytes = reader.read(temp);
//							// System.out.println("cleaning:" + numReadedBytes);
//							continue;
						} else
							cleaning = false;
//					}
					
					numReadedBytes = reader.read(buffer);//temp
					String tempStr = new String(buffer);//temp write to log
					tempStr.replace("\r\n", "***");
					String[] strs = tempStr.split("\r\n");
					// System.out.println("numReaded0:" + numReadedBytes);
					Log.d("texts",tempStr);
					sb = new StringBuffer();
//					for (int i = 0; i < numReadedBytes; i++) {
//						b = Integer.toHexString(buffer[i] & 0xFF);
//						if (b.length() < 2)
//							sb.append("0");
//						sb.append(b);
//					}
					mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_READ, numReadedBytes, 0, new String(buffer))
							.sendToTarget();
					buffer = new byte[44];
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		/**
		 * Write to the connected OutStream.
		 * 
		 * @param buffer
		 *            The bytes to write
		 */
		public void write(byte[] buffer) {
			try {
				mmOutStream.write(buffer);
				mHandler.obtainMessage(ConstantsBluetooth.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
			} catch (IOException e) {
				e.printStackTrace();
				Log.i("texts", "数据流写入失败");
			}
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
			}
		}
	}
}

OK,,,这样我所以项目都贴到上面了,,因为个人原因,不能贴项目源码,,但是改了一个项目,,亲测可用,,

我们要重点注意这里

MainActivity.java:

((Button)findViewById(R.id.btn1)).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				executStr = ((Button)v).getText().toString();
				output_edit.setText("");
				executStr+="\r\n";
				BluetoothEnvironment.service.write(executStr.getBytes());
				output_edit.setText(executStr);
			}
		});


这段是真实向下位机写数据,,如果你项目非常紧急,建议直接找到此处,修改这里,还有类似位置,然后我们在看看S端Return的值:

MainActivity.java:

case ConstantsBluetooth.MESSAGE_READ:
				if (firstReceived)
					lastReadTime = System.currentTimeMillis();
				firstReceived = false;

				if (System.currentTimeMillis() - lastReadTime > ConstantsEwins.DATAGRAM_INTERVAL) {
					if (length == ConstantsEwins.DATAGRAM_BYTES_LENGTH) {
						datagram.parseDatagram(sb.toString());
						modifyViews();
					}
					sb = new StringBuffer(ConstantsEwins.DATA_LENGTH);
					length = 0;
				}

				String data = (String) msg.obj;
				System.out.println("接收到的数据:" + data);
				Log.e("texts",data);
				length += msg.arg1;
				sb.append(data);
				lastReadTime = System.currentTimeMillis();
				input_edit.setText(data);//sb.toString()
				break;

以上即可与下位机交互,,,图片资源请自行寻找,,懒得贴了,,不做伸手党!!!




  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值