import 'dart:convert'; import 'dart:io'; import 'package:crypto/crypto.dart' as crypto; import 'package:flutter_sdk/starcloud_flutter_sdk.dart'; import 'package:starcloud/blue/blue_features.dart'; import 'package:starcloud/blue/blue_read_write.dart'; import 'package:starcloud/blue/entity/blue_message.dart'; import 'package:starcloud/device/ble/base/base_sender.dart'; import 'package:starcloud/device/ble/ble_reply.dart'; import 'package:starcloud/device/ble/business/ble_type.dart'; import 'package:starcloud/device/ble/tool/ble_tool.dart'; import 'package:starcloud/entity/star_cloud_lock_list.dart'; import 'package:starcloud/tool/sm4_encipher/sm4.dart'; /// 主动监控前将猫眼从省电模式切换到实时监控模式。 /// /// 实时模式由 BLE feature bit 64 写入锁端。 /// 省电模式(catEyeMode=0)下锁固件会忽略主动 TalkRequest。 /// /// 公开 Flutter SDK 文档未定义 cat-eye 云端设置 API;因此这里不能调用 /// 非公开的 `updateCatEyeConfig` 并把其服务端权限错误当作 BLE 操作失败。 class CatEyeMonitorService { static const int _catEyeFeatureBit = 64; static const int _realtimeMode = 2; Future enableRealtimeMode({ required StarCloudLock lock, }) async { final context = await StarCloudLockContext.fromLock(lock); final featureData = [ _realtimeMode, 0, 0, 0, 0, 0, 0, // 非自定义模式的录像参数 0, 0, // detectionDistance / realTimePicture 1, 10, 1, 1, // 自动亮屏、亮屏时长、逗留/异常告警 ]; final blue = BlueReadWrite.shareManager(); if (blue == null) { throw const StarCloudException('Bluetooth service is unavailable'); } blue.deviceName = lock.bluetooth.bluetoothDeviceName; if (!Platform.isAndroid) blue.mac = lock.mac; blue.type = BlueFeaturesType.lock; blue.clearCallback(); final reply = await blue.writeAndReturn( _SetCatEyeModeCommand( keyId: context.keyId, userId: context.userId, featureData: featureData, token: context.token, needAuthor: context.needAuthor, publicKey: context.publicKey, privateKey: context.privateKey, ), getPrivateKey: () => context.privateKey, timeout: const Duration(seconds: 20), ); if (reply.status != 0) { throw StarCloudException( 'Failed to enable cat-eye realtime mode over BLE', raw: 'status=0x${reply.status.toRadixString(16)}', ); } } } class _SetCatEyeModeCommand extends SenderProtocol { _SetCatEyeModeCommand({ required this.keyId, required this.userId, required this.featureData, required List token, required this.needAuthor, required this.publicKey, required this.privateKey, }) : token = List.from(token), super(CommandType.generalExtendedCommond); final String keyId; final String userId; final List featureData; final int needAuthor; final List publicKey; final List privateKey; @override List? token; @override List messageDetail() { final data = [0x30, 0x30, 72]; var subData = []; final keyBytes = utf8.encode(keyId); subData.addAll(keyBytes); subData = BLETool.getFixedLengthList(subData, 40 - keyBytes.length); final userBytes = utf8.encode(userId); subData.addAll(userBytes); subData = BLETool.getFixedLengthList(subData, 20 - userBytes.length); subData ..add(CatEyeMonitorService._catEyeFeatureBit) ..add(featureData.length) ..addAll(featureData); if (needAuthor == 0) { subData.add(0); } else { final authData = [ ...userBytes, ...keyBytes, ...?token, ...publicKey, ]; final authCode = crypto.md5.convert(authData).bytes; subData ..add(authCode.length) ..addAll(authCode); } data ..add(subData.length) ..addAll(subData); while (data.length % 16 != 0) { data.add(0); } return SM4.encrypt(data, key: privateKey, mode: SM4CryptoMode.ECB); } @override BLEReply blueMessageToReply(BlueMessage message) { return _SetCatEyeModeReply.parseData(commandType, message.data); } } class _SetCatEyeModeReply extends BLEReply { _SetCatEyeModeReply.parseData( CommandType commandType, List data, ) : super.parseData(commandType, data) { // 通用扩展应答:[0x30, 0x30, mainStatus, subCommand, subStatus, ...] // 原 App 以 mainStatus 为本次设置结果。 status = data.length > 2 ? data[2] : 0xff; errorWithStatus(status); if (status == 0x06 && data.length >= 9) { token = data.sublist(5, 9); } } }