152 lines
4.6 KiB
Dart
152 lines
4.6 KiB
Dart
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<void> enableRealtimeMode({
|
||
required StarCloudLock lock,
|
||
}) async {
|
||
final context = await StarCloudLockContext.fromLock(lock);
|
||
final featureData = <int>[
|
||
_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<int> token,
|
||
required this.needAuthor,
|
||
required this.publicKey,
|
||
required this.privateKey,
|
||
}) : token = List<int>.from(token),
|
||
super(CommandType.generalExtendedCommond);
|
||
|
||
final String keyId;
|
||
final String userId;
|
||
final List<int> featureData;
|
||
final int needAuthor;
|
||
final List<int> publicKey;
|
||
final List<int> privateKey;
|
||
|
||
@override
|
||
List<int>? token;
|
||
|
||
@override
|
||
List<int> messageDetail() {
|
||
final data = <int>[0x30, 0x30, 72];
|
||
var subData = <int>[];
|
||
|
||
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 = <int>[
|
||
...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<int> 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);
|
||
}
|
||
}
|
||
}
|