#连接库初步完成
This commit is contained in:
14
lib/core/network/base/i_connection.dart
Normal file
14
lib/core/network/base/i_connection.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
|
||||
import '../../error/failure.dart';
|
||||
|
||||
enum ConnectionStatus { connected, disconnected, connecting }
|
||||
|
||||
abstract class IConnection {
|
||||
Stream<List<int>> get rawStream;
|
||||
Stream<ConnectionStatus> get statusStream;
|
||||
Future<Either<Failure, Unit>> connect();
|
||||
Future<void> disconnect();
|
||||
Future<Either<Failure, Unit>> send(List<int> data);
|
||||
}
|
||||
3
lib/core/network/ble/ble_client.dart
Normal file
3
lib/core/network/ble/ble_client.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
class BleClient{
|
||||
|
||||
}
|
||||
1
lib/core/network/enums/control_mode.dart
Normal file
1
lib/core/network/enums/control_mode.dart
Normal file
@@ -0,0 +1 @@
|
||||
enum ControlMode { TCP, BLE, LOCAL, NONE, OTHER }
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:maibu_satabot_v2/core/consts/http_api_consts.dart';
|
||||
|
||||
import '../app/app_user_cubit.dart';
|
||||
import '../di/injection.dart';
|
||||
import '../../../features/auth/presentation/bloc/app_user_cubit.dart';
|
||||
import '../../di/injection.dart';
|
||||
|
||||
class DioClient {
|
||||
static Dio create() {
|
||||
17
lib/core/network/models/connection_config.dart
Normal file
17
lib/core/network/models/connection_config.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
class ConnectionConfig {
|
||||
final String address; // IP 地址或蓝牙 MAC/UUID
|
||||
final int? port; // TCP 专用端口
|
||||
|
||||
ConnectionConfig({
|
||||
required this.address,
|
||||
this.port,
|
||||
});
|
||||
|
||||
// 方便快捷创建 TCP 配置
|
||||
factory ConnectionConfig.tcp(String ip, int port) =>
|
||||
ConnectionConfig(address: ip, port: port);
|
||||
|
||||
// 方便快捷创建 BLE 配置
|
||||
factory ConnectionConfig.ble(String mac) =>
|
||||
ConnectionConfig(address: mac);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'protocol_decoder.dart';
|
||||
import 'tcp/tcp_client.dart';
|
||||
// 注意:这里需要导入你具体的 UserModel
|
||||
// import '../../features/auth/data/models/user_model.dart';
|
||||
|
||||
class NetMessageDispatcher {
|
||||
final TcpClient tcpClient;
|
||||
NetMessageDispatcher(this.tcpClient);
|
||||
|
||||
/// 过滤特定指令的流
|
||||
Stream<RawPacket> onCommand(int commandCode) {
|
||||
return tcpClient.packetStream.where((p) => p.command == commandCode);
|
||||
}
|
||||
|
||||
/// 示例解析方法:将 0x02 指令解析为 String
|
||||
Stream<String> onStringMessage() {
|
||||
return onCommand(0x02).map((p) => utf8.decode(p.payload));
|
||||
}
|
||||
|
||||
/// 示例解析方法:将 0x12 指令解析为 JSON 并转为模型
|
||||
Stream<Map<String, dynamic>> onJsonMessage(int cmd) {
|
||||
return onCommand(cmd).map((p) => jsonDecode(utf8.decode(p.payload)));
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class RawPacket {
|
||||
final int command; // 第三位指令位
|
||||
final Uint8List payload; // 数据位
|
||||
RawPacket({required this.command, required this.payload});
|
||||
}
|
||||
|
||||
class ProtocolDecoder {
|
||||
static const head = [0xAB, 0xAA];
|
||||
static const tail = [0xAA, 0xAB];
|
||||
final List<int> _buffer = [];
|
||||
|
||||
List<RawPacket> decode(List<int> newData) {
|
||||
_buffer.addAll(newData);
|
||||
List<RawPacket> packets = [];
|
||||
|
||||
while (_buffer.length >= 5) {
|
||||
// 最小包长度:头2+指令1+尾2 = 5,加1位数据=6,但是心跳包是AB AA FF AA AB
|
||||
// 1. 找头
|
||||
int headIdx = -1;
|
||||
for (int i = 0; i < _buffer.length - 1; i++) {
|
||||
if (_buffer[i] == head[0] && _buffer[i + 1] == head[1]) {
|
||||
headIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (headIdx == -1) {
|
||||
_buffer.clear(); // 没头,全是垃圾数据
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. 找尾
|
||||
int tailIdx = -1;
|
||||
for (int i = headIdx + 2; i < _buffer.length - 1; i++) {
|
||||
if (_buffer[i] == tail[0] && _buffer[i + 1] == tail[1]) {
|
||||
tailIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tailIdx != -1) {
|
||||
// 3. 提取
|
||||
int cmd = _buffer[headIdx + 2];
|
||||
Uint8List payload = Uint8List.fromList(
|
||||
_buffer.sublist(headIdx + 3, tailIdx),
|
||||
);
|
||||
packets.add(RawPacket(command: cmd, payload: payload));
|
||||
|
||||
// 4. 移除已处理部分
|
||||
_buffer.removeRange(0, tailIdx + 2);
|
||||
} else {
|
||||
break; // 还没传完
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:maibu_satabot_v2/core/network/protocol_decoder.dart';
|
||||
|
||||
class TcpClient {
|
||||
Socket? _socket;
|
||||
final _decoder = ProtocolDecoder();
|
||||
// 使用 StreamController 将原始数据流暴露给外部,以便多个 DataSource 监听
|
||||
final _controller = StreamController<RawPacket>.broadcast();
|
||||
Stream<RawPacket> get packetStream => _controller.stream;
|
||||
|
||||
// 通过参数配置,不硬编码
|
||||
Future<void> connect({required String host, required int port}) async {
|
||||
try {
|
||||
_socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
_socket!.listen(
|
||||
(data) {
|
||||
var packets = _decoder.decode(data);
|
||||
for (var packet in packets) {
|
||||
_controller.add(packet);
|
||||
}
|
||||
},
|
||||
onDone: disconnect,
|
||||
onError: (e) => disconnect(),
|
||||
);
|
||||
} catch (e) {
|
||||
rethrow; // 向上抛出连接异常
|
||||
}
|
||||
}
|
||||
|
||||
void send(Map<String, dynamic> data) {
|
||||
if (_socket == null) throw Exception("Socket not connected");
|
||||
_socket!.write(jsonEncode(data));
|
||||
}
|
||||
|
||||
void sendRaw(int cmd, List<int> payload) {
|
||||
if (_socket == null) return;
|
||||
|
||||
final builder = BytesBuilder()
|
||||
..addByte(0xAB)
|
||||
..addByte(0xAA)
|
||||
..addByte(cmd)
|
||||
..add(payload)
|
||||
..add([0x00, 0x00]) // CRC
|
||||
..addByte(0xAA)
|
||||
..addByte(0xAB);
|
||||
|
||||
_socket!.add(builder.takeBytes());
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
_socket?.destroy();
|
||||
_socket = null;
|
||||
_controller.close();
|
||||
debugPrint("TCP Disconnected");
|
||||
}
|
||||
}
|
||||
53
lib/core/network/tcp/tcp_connection.dart
Normal file
53
lib/core/network/tcp/tcp_connection.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
|
||||
import '../../error/failure.dart';
|
||||
import '../base/i_connection.dart';
|
||||
|
||||
class TcpConnection implements IConnection {
|
||||
final String host;
|
||||
final int port;
|
||||
Socket? _socket;
|
||||
final _statusController = StreamController<ConnectionStatus>.broadcast();
|
||||
|
||||
TcpConnection({required this.host, required this.port});
|
||||
|
||||
@override
|
||||
Stream<List<int>> get rawStream => _socket ?? const Stream.empty();
|
||||
@override
|
||||
Stream<ConnectionStatus> get statusStream => _statusController.stream;
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Unit>> connect() async {
|
||||
try {
|
||||
_statusController.add(ConnectionStatus.connecting);
|
||||
_socket = await Socket.connect(host, port, timeout: const Duration(seconds: 5));
|
||||
_statusController.add(ConnectionStatus.connected);
|
||||
_socket!.done.then((_) => _statusController.add(ConnectionStatus.disconnected));
|
||||
return right(unit);
|
||||
} catch (e) {
|
||||
_statusController.add(ConnectionStatus.disconnected);
|
||||
return left(NetworkFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, Unit>> send(List<int> data) async {
|
||||
if (_socket == null) return left(NetworkFailure("Socket未连接"));
|
||||
try {
|
||||
_socket!.add(data);
|
||||
await _socket!.flush(); // 立即刷出
|
||||
return right(unit);
|
||||
} catch (e) {
|
||||
return left(NetworkFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> disconnect() async {
|
||||
await _socket?.close();
|
||||
_socket = null;
|
||||
_statusController.add(ConnectionStatus.disconnected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user