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
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;
|
|
}
|
|
|