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