You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

82 lines
3.7 KiB

import 'dart:async';
import 'package:flutter/services.dart';
/// Smartcar(车载香薰)原生链路的 Dart 通道封装。
///
/// 只做三件事:扫描、连接(**含杰理 RCSP 认证**)、收发 C2 私有通道的裸字节。
/// 组帧 / CRC / 广播解析都在 App 侧的 `smartcar_protocol.dart`,这里不解释任何字节。
///
/// `connect()` 只在**认证通过**后才返回——固件在那之前不把手机当成已连接。
class DeviceSmartcar {
DeviceSmartcar._();
static final DeviceSmartcar instance = DeviceSmartcar._();
static const MethodChannel _method = MethodChannel('com.yimai.device_smartcar');
static const EventChannel _events = EventChannel('com.yimai.device_smartcar/events');
Stream<Map<String, dynamic>>? _stream;
/// 原生事件(广播流)。每条形如 `{type: scan|scanStopped|state|custom|adapter, ...}`。
Stream<Map<String, dynamic>> get events {
return _stream ??= _events
.receiveBroadcastStream()
.map((e) => Map<String, dynamic>.from(e as Map))
.asBroadcastStream();
}
Future<String> adapterState() async =>
(await _method.invokeMethod<String>('adapterState')) ?? 'unknown';
/// 起不来抛 [PlatformException](code `SCAN_FAILED`,message 是原因)。
Future<void> startScan({int timeoutSec = 15}) =>
_method.invokeMethod<bool>('startScan', {'timeoutSec': timeoutSec});
Future<void> stopScan() => _method.invokeMethod<void>('stopScan');
/// 认证通过才 resolve;失败抛 [PlatformException](code `CONNECT_FAILED` / `NOT_FOUND` / `BUSY`)。
Future<void> connect(String id, {int timeoutSec = 45}) =>
_method.invokeMethod<bool>('connect', {'id': id, 'timeoutSec': timeoutSec});
Future<void> disconnect() => _method.invokeMethod<void>('disconnect');
Future<bool> isConnected() async =>
(await _method.invokeMethod<bool>('isConnected')) ?? false;
/// 认证通过后的杰理 RCSP 收尾序列(设时间 / GET_TARGET_INFO / GET_SYS_INFO),
/// 返回各步的 JL_CMDStatus(0 = 成功)。未认证抛 `NOT_READY`。
Future<Map<String, dynamic>> jlPostAuth() async {
final r = await _method.invokeMethod<Map>('jlPostAuth');
return Map<String, dynamic>.from(r ?? const {});
}
/// 开始把设备麦克风的 opus 上行解码成 16k PCM 往上抛(`{type:'audio'}` 事件)。
///
/// ⚠️ **不控制设备开麦**:设备是收到 `0xCC 0x11` 唤醒后自己开麦的,
/// 这里只决定原生要不要解码转发。返回 false = opus 解码器起不来。
Future<bool> audioStart() async =>
(await _method.invokeMethod<bool>('audioStart')) ?? false;
Future<void> audioStop() => _method.invokeMethod<void>('audioStop');
/// 排查用:`{running, framesIn, framesDup, pcmBytesOut}`。
/// 真机"没声音"时先看这几个数,能立刻区分是没收到帧、全被当重复丢了、还是解码没出字节。
Future<Map<String, dynamic>> audioStats() async {
final r = await _method.invokeMethod<Map>('audioStats');
return Map<String, dynamic>.from(r ?? const {});
}
/// 启动杰理 OTA(原生含 stage2 扫描回连)。进度从 [events] 的 `{type:'ota'}` 来。
/// 起不来抛 [PlatformException](`OTA_FAILED` / `NOT_READY`)。
Future<void> otaStart(String filePath) =>
_method.invokeMethod<bool>('otaStart', {'filePath': filePath});
Future<void> otaCancel() => _method.invokeMethod<void>('otaCancel');
Future<bool> otaIsRunning() async =>
(await _method.invokeMethod<bool>('otaIsRunning')) ?? false;
/// 往 ABC1 写一帧(已组好、含 CRC 的裸字节)
Future<bool> writeCustom(Uint8List data) async =>
(await _method.invokeMethod<bool>('writeCustom', {'data': data})) ?? false;
}