package com.wrs.project.serialportdemo;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.wrs.project.module.serialport.Device;
import com.wrs.project.module.serialport.SerialPortFinder;
import com.wrs.project.module.serialport.SerialPortManager;
import com.wrs.project.module.serialport.listener.OnSerialPortListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
public class BluetoothActivity extends Activity implements OnSerialPortListener {
private static final String TAG = "BluetoothMusic";
// 协议类型常量
private static final byte TYPE_CONNECTION_STATUS = 0x55; // 新增连接状态协议
private static final byte TYPE_CURRENT_TIME = 0x56;
private static final byte TYPE_TOTAL_TIME = 0x57;
private static final byte TYPE_LYRIC = 0x58;
private static final byte TYPE_SONG_INFO = 0x59;
private static final byte TYPE_ALBUM = 0x5A;
// 协议头部和尾部常量
private static final byte HEADER_STATUS = (byte) 0xAA; // 连接状态头部
private static final byte HEADER1 = (byte) 0xA9; // 对应0X56
private static final byte HEADER2 = (byte) 0xA8; // 对应0X57
private static final byte HEADER3 = (byte) 0xA7; // 对应0X58
private static final byte HEADER4 = (byte) 0xA6; // 对应0X59
private static final byte HEADER5 = (byte) 0xA5; // 对应0X5A
private static final byte FOOTER = (byte) 0xEF;
// 字符编码设置(支持中文显示)
private static final String[] CHARSETS_TO_TRY = {"GBK", "UTF-8", "GB2312", "ISO-8859-1"};
private Button btBack, btPrev, btNext, btPP;
private TextView tvCurrentTime, tvTotalTime, tvLyric, tvSongInfo, tvAlbum, tvConnectionStatus;
private TextView tvTitle, tvStatusTitle, tvTimeTitle, tvLyricsTitle, tvAlbumTitle;
private SerialPortManager serialPortManager;
private boolean isConnected = false;
// 数据缓冲区
private final ByteArrayOutputStream dataBuffer = new ByteArrayOutputStream();
// 蓝牙控制命令
private static final String CMD_BACK = "AA5504EF";
private static final String CMD_PREV = "AA5501EF";
private static final String CMD_NEXT = "AA5502EF";
private static final String CMD_PLAY_PAUSE = "AA5500EF";
private static final String CMD_INIT = "AA5503EF";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(android.R.style.Theme_NoTitleBar);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_bluetooth);
initViews();
initSerialPort();
setupButtonListeners();
}
private void initViews() {
// 初始化标题视图
tvTitle = findViewById(R.id.tv_title);
tvTitle.setText("蓝牙音乐播放器");
tvTitle.setTextSize(24);
tvTitle.setTextColor(Color.parseColor("#4A90E2"));
tvTitle.setTypeface(null, Typeface.BOLD);
tvStatusTitle = findViewById(R.id.tv_status_title);
tvStatusTitle.setText("连接状态:");
tvTimeTitle = findViewById(R.id.tv_time_title);
tvTimeTitle.setText("播放进度:");
tvLyricsTitle = findViewById(R.id.tv_lyrics_title);
tvLyricsTitle.setText("歌词:");
tvAlbumTitle = findViewById(R.id.tv_album_title);
tvAlbumTitle.setText("专辑:");
// 初始化信息视图
tvConnectionStatus = findViewById(R.id.tv_connection_status);
tvConnectionStatus.setText("未连接");
tvConnectionStatus.setTextColor(Color.RED);
tvCurrentTime = findViewById(R.id.tv_current_time);
tvCurrentTime.setText("00:00");
tvCurrentTime.setTextSize(18);
tvTotalTime = findViewById(R.id.tv_total_time);
tvTotalTime.setText("00:00");
tvTotalTime.setTextSize(18);
tvLyric = findViewById(R.id.tv_lyric);
tvLyric.setText("等待歌词...");
tvLyric.setTextSize(20);
tvLyric.setTextColor(Color.parseColor("#333333"));
tvSongInfo = findViewById(R.id.tv_song_info);
tvSongInfo.setText("未播放");
tvSongInfo.setTextSize(22);
tvSongInfo.setTextColor(Color.BLACK);
tvSongInfo.setTypeface(null, Typeface.BOLD);
tvAlbum = findViewById(R.id.tv_album);
tvAlbum.setText("未知专辑");
tvAlbum.setTextSize(18);
tvAlbum.setTextColor(Color.parseColor("#666666"));
// 初始化按钮
btBack = findViewById(R.id.bt_back);
btPrev = findViewById(R.id.bt_prev);
btNext = findViewById(R.id.bt_next);
btPP = findViewById(R.id.bt_pp);
// 设置按钮样式
int buttonColor = Color.parseColor("#4A90E2");
btBack.setBackgroundColor(buttonColor);
btPrev.setBackgroundColor(buttonColor);
btNext.setBackgroundColor(buttonColor);
btPP.setBackgroundColor(buttonColor);
btBack.setTextColor(Color.WHITE);
btPrev.setTextColor(Color.WHITE);
btNext.setTextColor(Color.WHITE);
btPP.setTextColor(Color.WHITE);
}
private void initSerialPort() {
serialPortManager = new SerialPortManager();
SerialPortFinder serialPortFinder = new SerialPortFinder();
List<Device> devices = serialPortFinder.getDevices();
if (devices != null && devices.size() > 1) {
Device device = devices.get(1);
try {
serialPortManager.openSerialPort(device.getFile(), 1500000, 3, 0, 3, 0);
serialPortManager.setOnSerialPortListener(this);
sendCommand(CMD_INIT); // 发送初始化命令
showToast("串口初始化成功");
tvConnectionStatus.setText("初始化中...");
tvConnectionStatus.setTextColor(Color.parseColor("#FFA500"));
// 添加状态查询定时器
// startStatusQuery();
} catch (Exception e) {
Log.e(TAG, "串口打开失败", e);
showToast("串口初始化失败");
}
} else {
showToast("未找到可用串口设备");
}
}
// 添加状态查询定时器
// private void startStatusQuery() {
// new Thread(() -> {
// while (!Thread.interrupted()) {
// try {
// Thread.sleep(3000); // 每3秒查询一次状态
// if (serialPortManager != null && serialPortManager.isOpened()) {
// sendCommand(CMD_INIT);
// }
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
// }
// }).start();
// }
private void setupButtonListeners() {
btBack.setOnClickListener(v -> handleButtonClick(CMD_BACK, "退出蓝牙模式"));
btPrev.setOnClickListener(v -> handleButtonClick(CMD_PREV, "上一曲"));
btNext.setOnClickListener(v -> handleButtonClick(CMD_NEXT, "下一曲"));
btPP.setOnClickListener(v -> handleButtonClick(CMD_PLAY_PAUSE, "播放/暂停"));
}
private void handleButtonClick(String command, String actionName) {
if (sendCommand(command)) {
if (CMD_BACK.equals(command)) {
finish(); // 退出命令特殊处理
}
showToast(actionName);
}
}
private boolean sendCommand(String hexCommand) {
if (serialPortManager == null || !serialPortManager.isOpened()) {
showToast("串口未就绪");
return false;
}
try {
byte[] data = ByteUtils.hexStrToByteArray(hexCommand);
serialPortManager.sendBytes(data);
Log.i(TAG, "发送命令: " + hexCommand);
return true;
} catch (Exception e) {
Log.e(TAG, "命令发送失败: " + hexCommand, e);
return false;
}
}
@Override
public void onDataReceived(byte[] bytes) {
// 将新数据添加到缓冲区
dataBuffer.write(bytes, 0, bytes.length);
// 处理缓冲区中的所有完整数据包
processDataBuffer();
}
private void processDataBuffer() {
byte[] bufferData = dataBuffer.toByteArray();
int startIndex = 0;
while (startIndex < bufferData.length) {
// 查找可能的协议起始位置
int packetStart = findPacketStart(bufferData, startIndex);
if (packetStart == -1) {
// 没有找到有效数据包,清空缓冲区
dataBuffer.reset();
return;
}
// 查找数据包结束位置 (0xEF)
int packetEnd = -1;
for (int i = packetStart; i < bufferData.length; i++) {
if (bufferData[i] == FOOTER) {
packetEnd = i;
break;
}
}
if (packetEnd == -1) {
// 没有找到完整的数据包,保留剩余数据
byte[] remaining = Arrays.copyOfRange(bufferData, packetStart, bufferData.length);
dataBuffer.reset();
dataBuffer.write(remaining, 0, remaining.length);
return;
}
// 提取完整数据包
byte[] packet = Arrays.copyOfRange(bufferData, packetStart, packetEnd + 1);
// 打印原始数据包用于调试
Log.d(TAG, "原始数据包: " + ByteUtils.bytesToHexString(packet));
// 处理数据包
processBluetoothPacket(packet);
// 移动起始位置到下一个包
startIndex = packetEnd + 1;
}
// 所有数据已处理,清空缓冲区
dataBuffer.reset();
}
private int findPacketStart(byte[] data, int startIndex) {
for (int i = startIndex; i < data.length - 1; i++) {
byte type = data[i];
byte header = data[i + 1];
// 新增连接状态协议检测 (0x55 0xAA)
if (type == TYPE_CONNECTION_STATUS && header == HEADER_STATUS) {
return i;
}
if ((type == TYPE_CURRENT_TIME && header == HEADER1) ||
(type == TYPE_TOTAL_TIME && header == HEADER2) ||
(type == TYPE_LYRIC && header == HEADER3) ||
(type == TYPE_SONG_INFO && header == HEADER4) ||
(type == TYPE_ALBUM && header == HEADER5)) {
return i;
}
}
return -1;
}
private void processBluetoothPacket(byte[] packet) {
if (packet.length < 3) {
Log.w(TAG, "无效数据包:长度不足");
return;
}
byte type = packet[0];
// packet[1] 是头部标识,已通过findPacketStart验证
// 提取有效数据(去掉类型、头部和尾部)
byte[] data = Arrays.copyOfRange(packet, 2, packet.length - 1);
// 处理连接状态协议 (新增)
if (type == TYPE_CONNECTION_STATUS) {
// 详细解析信息
StringBuilder analysis = new StringBuilder();
analysis.append("连接状态协议解析:\n");
analysis.append("类型: 0x").append(String.format("%02X", type)).append("\n");
analysis.append("头部: 0x").append(String.format("%02X", packet[1])).append("\n");
analysis.append("状态字节: ");
if (data.length >= 6) {
byte statusByte = data[0];
analysis.append("0x").append(String.format("%02X", statusByte)).append("\n");
String statusText;
int color;
switch (statusByte) {
case (byte) 0x7A:
statusText = "已连接";
color = Color.GREEN;
break;
case (byte) 0x7C:
statusText = "未连接";
color = Color.RED;
break;
case (byte) 0x30: // 处理0x30状态
statusText = "蓝牙连接中";
color = Color.parseColor("#FFA500");
break;
default:
statusText = "未知状态: 0x" + String.format("%02X", statusByte);
color = Color.parseColor("#FFA500");
}
analysis.append("状态解释: ").append(statusText);
Log.i(TAG, analysis.toString());
// addDebugLog(analysis.toString());
runOnUiThread(() -> {
tvConnectionStatus.setText(statusText);
tvConnectionStatus.setTextColor(color);
});
} else {
analysis.append("数据长度不足");
Log.w(TAG, analysis.toString());
// addDebugLog(analysis.toString());
}
return;
}
// 将字节数据转换为字符串,尝试多种编码(解决中文乱码问题)
String dataString = decodeDataWithMultipleEncodings(data);
// 如果解码失败,使用十六进制表示
if (dataString == null) {
dataString = "无法解码: " + ByteUtils.bytesToHexString(data);
}
String finalDataString = dataString;
runOnUiThread(() -> {
switch (type) {
case TYPE_CURRENT_TIME:
tvCurrentTime.setText(formatTime(finalDataString));
break;
case TYPE_TOTAL_TIME:
tvTotalTime.setText(formatTime(finalDataString));
break;
case TYPE_LYRIC:
tvLyric.setText(finalDataString);
// 歌词高亮效果
tvLyric.setTextColor(Color.parseColor("#E91E63"));
tvLyric.postDelayed(() -> tvLyric.setTextColor(Color.parseColor("#333333")), 500);
break;
case TYPE_SONG_INFO:
tvSongInfo.setText(finalDataString);
break;
case TYPE_ALBUM:
tvAlbum.setText(finalDataString);
break;
default:
Log.w(TAG, "未知数据类型: " + type);
}
});
}
// 尝试多种编码方式解码数据(解决中文乱码问题)
private String decodeDataWithMultipleEncodings(byte[] data) {
if (data == null || data.length == 0) return "";
// 优先尝试UTF-8
String result = tryDecode(data, StandardCharsets.UTF_8);
if (isValidString(result)) return result;
// 尝试GBK
result = tryDecode(data, Charset.forName("GBK"));
if (isValidString(result)) return result;
// 尝试GB2312
result = tryDecode(data, Charset.forName("GB2312"));
if (isValidString(result)) return result;
// 尝试ISO-8859-1
result = tryDecode(data, StandardCharsets.ISO_8859_1);
if (isValidString(result)) return result;
// 所有尝试失败
Log.w(TAG, "所有编码尝试失败,使用原始十六进制");
return null;
}
private String tryDecode(byte[] data, Charset charset) {
try {
return new String(data, charset);
} catch (Exception e) {
Log.w(TAG, "解码失败: " + charset.name(), e);
return null;
}
}
private boolean isValidString(String str) {
if (str == null) return false;
// 检查是否包含过多无效字符( 或?)
int invalidCount = 0;
for (char c : str.toCharArray()) {
if (c == ' ' || c == '?') {
invalidCount++;
}
}
// 如果无效字符超过总字符数的20%,则认为解码失败
return (invalidCount * 1.0 / str.length()) < 0.2;
}
// 格式化时间显示 (假设时间格式为秒数或MM:SS)
private String formatTime(String time) {
try {
if (time.contains(":")) {
return time; // 已经是MM:SS格式
}
int seconds = Integer.parseInt(time);
int minutes = seconds / 60;
int remainingSeconds = seconds % 60;
return String.format("%02d:%02d", minutes, remainingSeconds);
} catch (NumberFormatException e) {
return time; // 无法解析则返回原始字符串
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (serialPortManager != null) {
serialPortManager.closeSerialPort();
}
}
// 辅助方法
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
// 以下接口方法根据需要实现
@Override public void onSuccess(File device) {}
@Override public void onFail(File device, Status status) {}
@Override public void onClose(File device) {}
@Override public void onDataSent(byte[] bytes) {}
}
分析下这段代码,当前时间和总时间改成:
当前时间:分:数据转为十进制/1000/60, 秒:数据转为十进制后/1000%60
总时间:分:数据转为十进制/1000/60, 秒:数据转为十进制后/1000%60
最新发布