支持泰凌微方案
authorliangzhenying <867174124@qq.com>
Thu, 22 Aug 2024 07:34:11 +0000 (15:34 +0800)
committerliangzhenying <867174124@qq.com>
Thu, 22 Aug 2024 07:34:11 +0000 (15:34 +0800)
43 files changed:
Document/AiMouseSdk接口文档.md
Document/AiMouseSdk接口文档.pdf
MouseSdk/AiMouseSdk/AiMouseSdk.xcodeproj/project.pbxproj
MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.h [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.m [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.c [new file with mode: 0644]
MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.h [new file with mode: 0644]
MouseSdk/MouseSdk.xcodeproj/project.pbxproj
MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate
MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
MouseSdk/MouseSdk/Ble/BleManager.h [moved from MouseSdk/MouseSdk/BleManager.h with 84% similarity]
MouseSdk/MouseSdk/Ble/BleManager.m [moved from MouseSdk/MouseSdk/BleManager.m with 96% similarity]
MouseSdk/MouseSdk/Ble/DeviceManger.h [moved from MouseSdk/MouseSdk/DeviceManger.h with 73% similarity]
MouseSdk/MouseSdk/Ble/DeviceManger.m [moved from MouseSdk/MouseSdk/DeviceManger.m with 84% similarity]
MouseSdk/MouseSdk/Ble/HidManager.h [moved from MouseSdk/MouseSdk/HidManager.h with 95% similarity]
MouseSdk/MouseSdk/Ble/HidManager.m [moved from MouseSdk/MouseSdk/HidManager.m with 87% similarity]
MouseSdk/MouseSdk/FrkVC.h [new file with mode: 0644]
MouseSdk/MouseSdk/FrkVC.m [new file with mode: 0644]
MouseSdk/MouseSdk/MouseSdkManager.h [new file with mode: 0644]
MouseSdk/MouseSdk/MouseSdkManager.m [new file with mode: 0644]
MouseSdk/MouseSdk/ScanVC.m
MouseSdk/MouseSdk/Tlw/TlwBleManager.h [new file with mode: 0644]
MouseSdk/MouseSdk/Tlw/TlwBleManager.m [new file with mode: 0644]
MouseSdk/MouseSdk/Tlw/TlwDeviceManger.h [new file with mode: 0644]
MouseSdk/MouseSdk/Tlw/TlwDeviceManger.m [new file with mode: 0644]
MouseSdk/MouseSdk/Tlw/TlwHidManager.h [new file with mode: 0644]
MouseSdk/MouseSdk/Tlw/TlwHidManager.m [new file with mode: 0644]
MouseSdk/MouseSdk/TlwScanVC.h [new file with mode: 0644]
MouseSdk/MouseSdk/TlwScanVC.m [new file with mode: 0644]
MouseSdk/MouseSdk/TlwScanVC.xib [new file with mode: 0644]
MouseSdk/MouseSdk/TlwVC.h [new file with mode: 0644]
MouseSdk/MouseSdk/TlwVC.m [new file with mode: 0644]
MouseSdk/MouseSdk/ViewController.m

index 14ab19f..51b6dd9 100644 (file)
@@ -4,11 +4,11 @@
 \r
 ## 介绍\r
 \r
-当前版本:v1.0.31\r
+当前版本:v1.0.32\r
 \r
-更新内容20240816:\r
+更新内容20240821:\r
 \r
-1. æ\94¯æ\8c\81é\98¿é\87\8cé\89´æ\9d\83\r
+1. æ\96°å¢\9eæ\94¯æ\8c\81æ³°å\87\8cå¾®æ\96¹æ¡\88\r
 \r
 ## 环境要求\r
 \r
@@ -18,7 +18,7 @@ MacOS 13.0
 \r
 ## 引入依赖库\r
 \r
-将 libAiMouseSdk2 添加进项目中。然后,在 Build Phases 选项卡中,添加以下框架和库:\r
+将 sdk 添加进项目中。然后,在 Build Phases 选项卡中,添加以下框架和库:\r
 \r
 libz.tbd、libbz2.tbd、libiconv.tbd、liblzma5.tbd、CoreAudio.framework、VideoToolbox.framework\r
 \r
@@ -46,7 +46,11 @@ libz.tbd、libbz2.tbd、libiconv.tbd、liblzma5.tbd、CoreAudio.framework、Vide
 \r
 ![image-20240401162536522](/Users/apple/Library/Application Support/typora-user-images/image-20240401162536522.png)\r
 \r
-## SDK相关接口\r
+## 富瑞坤方案\r
+\r
+\r
+\r
+### SDK相关接口\r
 \r
 ```objective-c\r
 /// 单利\r
@@ -145,7 +149,7 @@ libz.tbd、libbz2.tbd、libiconv.tbd、liblzma5.tbd、CoreAudio.framework、Vide
 - (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;\r
 ```\r
 \r
-## SDK相关代理\r
+### SDK相关代理\r
 \r
 \r
 \r
@@ -187,7 +191,7 @@ libz.tbd、libbz2.tbd、libiconv.tbd、liblzma5.tbd、CoreAudio.framework、Vide
 \r
 ```\r
 \r
-## 其他类型数据\r
+### 其他类型数据\r
 \r
 ```objective-c\r
 \r
@@ -229,7 +233,7 @@ typedef NS_ENUM(NSInteger,AiMouseAuthState) {
 \r
 \r
 \r
-## 蓝牙示例\r
+### 蓝牙示例\r
 \r
 ```objective-c\r
 //\r
@@ -498,7 +502,7 @@ typedef NS_ENUM(NSInteger,AiMouseAuthState) {
 \r
 \r
 \r
-## USB示例\r
+### USB示例\r
 \r
 ​    \r
 \r
@@ -773,10 +777,635 @@ static uint32_t IOHIDDevGetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStr
 \r
 ```\r
 \r
+## 泰凌微方案\r
+\r
+### sdk相关接口\r
+\r
+```objective-c\r
+/// 单利\r
++(TlwMouse *)shared;\r
+\r
+/// 设置日志等级\r
+/// - Parameter level: 等级\r
+- (void)setLogLevel:(TlwMouseLogLevel)level;\r
+\r
+/// 断开连接\r
+- (void)didDisconnect;\r
+\r
+/// 切换连接类型\r
+/// - Parameter type: 连接类型\r
+- (void)switchConnect:(TlwConnectType)type;\r
+\r
+/// 传输业务数据给sdk\r
+/// - Parameter data:ai特征传过来的数据\r
+- (void)sendAiDataToSdk:(NSData *)data;\r
+\r
+///  传输设备信息数据给sdk\r
+/// - Parameter data:设备信息特征传过来的数据\r
+- (void)sendDeviceDataToSdk:(NSData *)data;\r
+\r
+/// 获取设备信息\r
+/// - Parameter completation: 如果获取成功,则deviceInfo有值,否则为nil\r
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))complete;\r
+\r
+/// 设置DPI\r
+/// - Parameters:\r
+///   - value: 数值1 ~ 4 (分别对应 800 1200 1800 2400)\r
+///   - finished: 设置结果回调\r
+- (void)setDPI:(uint8_t)value completation:(void (^)(BOOL isSuccess,NSError *error))finished;\r
+\r
+/// 打开录音\r
+/// - finished: 执行结果回调\r
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished;\r
+\r
+/// 关闭录音\r
+/// - finished: 执行结果回调\r
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;\r
+```\r
+\r
+### sdk相关代理\r
+\r
+```objective-c\r
+@protocol TlwMouseDelegate <NSObject>\r
+\r
+/// 设备连接状态\r
+/// - Parameter connected: 是否连接\r
+- (void)aiMouseConnectStateDidChange:(BOOL)connected;\r
+\r
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备\r
+/// - Parameter data: 数据\r
+- (void)tlwMouseSendDeviceInfoData:(NSData *)data;\r
+\r
+/// 鼠标按键指令回调\r
+/// - Parameter data: 数据。\r
+/// 前  单击0x06, 按下0x13(开始录音)抬起0x14(结束录音)\r
+/// 中  单击0x03, 按下0x11(开始录音)抬起0x12(结束录音)\r
+/// 后  第一次单击0x01, 第二次单击0x02(结束录音) , 按下0x09(开始录音)抬起0x10(结束录音)\r
+/// 其他未定义\r
+- (void)tlwMouseSendCmdData:(NSData *)data;\r
+\r
+/// 录音回调\r
+/// - Parameter data: pcm格式语音数据\r
+- (void)tlwMouseSendRecordData:(NSData *)data;\r
+\r
+@optional\r
+/// 输出sdk日志\r
+/// - Parameter log: 日志\r
+- (void)aiMouseAddLog:(NSString *)log;\r
+\r
+@end\r
+```\r
+\r
+### 其他数据类型\r
+\r
+```objective-c\r
+@interface TlwDeviceInfo : NSObject\r
+@property (nonatomic, assign) int DPI;/// DPI值 数值1 ~ 4 (分别对应 800 1200 1800 2400)\r
+@property (nonatomic, assign) int version;///软件版本\r
+@property (nonatomic, assign) int batteryState;/// 电池状态\r
+@property (nonatomic, assign) int batteryValue;/// 电池百分百电量,0-100\r
+@end\r
+\r
+/// 日志等级\r
+typedef NS_ENUM(NSInteger,TlwMouseLogLevel) {\r
+    TlwMouseLogLevel_none = 0,\r
+    TlwMouseLogLevel_error,\r
+    TlwMouseLogLevel_info,\r
+};\r
+\r
+typedef NS_ENUM(NSInteger,TlwConnectType) {\r
+    TlwConnectType_bluetooth = 0,///蓝牙\r
+    TlwConnectType_hid,///hid\r
+};\r
+```\r
+\r
+### 蓝牙示例\r
+\r
+```objective-c\r
+//\r
+//  BleManager.m\r
+//  AliIotSdkDemo\r
+//\r
+//  Created by incar on 2024/1/30.\r
+//\r
+\r
+#import "BleManager.h"\r
+#import "AiMouseSdk.h"\r
+#import "Config.h"\r
+\r
+// 服务id\r
+#define kServiceUUID @"FFF1"\r
+//ai特征\r
+#define kAiUUID @"0783B03E-8535-B5A0-7140-A304D2194CB8"\r
+//设备信息特征\r
+#define kDeviceInfoUUID @"0783B63E-8535-B5A0-7140-A304D2495CB6"\r
+\r
+@interface BleManager ()<CBCentralManagerDelegate,CBPeripheralDelegate,AiMouseDelegate>\r
+@property(nonatomic, strong) CBCentralManager *centralManager;///蓝牙管理\r
+@property(nonatomic, strong) CBPeripheral *connectPeripheral;//连接的设备\r
+@property(nonatomic, strong) CBCharacteristic *aiCha;//ai特征\r
+@property(nonatomic, strong) CBCharacteristic *deviceInfoCha;//设备信息特征\r
+@property (nonatomic,strong) NSMutableData *recordData;//录音的数据\r
+@end\r
+\r
+@implementation BleManager\r
+- (instancetype)init{\r
+    self = [super init];\r
+    if (self) {\r
+        self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];\r
+    }\r
+    return self;\r
+}\r
+\r
+#pragma mark - CBCentralManagerDelegate\r
+- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {\r
+    [self showLog:[NSString stringWithFormat:@"蓝牙状态 = %@",@(central.state)]];\r
+    switch (central.state) {\r
+        case CBManagerStateUnknown:\r
+            \r
+            break;\r
+        case CBManagerStateResetting:\r
+            \r
+            break;\r
+        case CBManagerStateUnsupported:\r
+            \r
+            break;\r
+        case CBManagerStateUnauthorized:\r
+            \r
+            break;\r
+        case CBManagerStatePoweredOff:\r
+            \r
+            break;\r
+        case CBManagerStatePoweredOn:\r
+            [self autoConnectDevice];\r
+            break;\r
+    }\r
+}\r
+\r
+- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{\r
+//    [self showLog:[NSString stringWithFormat:@"扫描到设备 = %@, data = %@",peripheral,advertisementData]];\r
+    if(self.discoverDevice){\r
+        self.discoverDevice(peripheral);\r
+    }\r
+}\r
+\r
+- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{\r
+    [self showLog:[NSString stringWithFormat:@"连接成功 name = %@",peripheral.name]];\r
+    if(self.didConnectDevice){\r
+        self.didConnectDevice(peripheral);\r
+    }\r
+    peripheral.delegate = self;\r
+    [peripheral discoverServices:nil];\r
+    \r
+    [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidConnectBleNotification object:nil];\r
+}\r
+-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{\r
+    [self showLog:[NSString stringWithFormat:@"断开连接。。。%@",error.description]];\r
+    [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidDisconnectBleNotification object:nil];\r
+    if(self.didDisConnectDevice){\r
+        self.didDisConnectDevice(peripheral);\r
+    }\r
+//    [self aiMouseConnectStateDidChange:false];\r
+    [[AiMouse shared] didDisconnect];\r
+}\r
+- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{\r
+    [self showLog:[NSString stringWithFormat:@"连接失败。。。%@",error.description]];\r
+}\r
+#pragma mark - CBPeripheralDelegate\r
+//发现服务\r
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {\r
+    for (CBService *service in peripheral.services){\r
+        [peripheral discoverCharacteristics:nil forService:service];\r
+    }\r
+}\r
+// 发现特征\r
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {\r
+    for (CBCharacteristic *characteristic in service.characteristics){\r
+        if ([characteristic.UUID.UUIDString isEqualToString:kAiUUID]) {\r
+            //AI特征\r
+            self.aiCha = characteristic;\r
+            [peripheral setNotifyValue:YES forCharacteristic:characteristic];\r
+        }\r
+        else if ([characteristic.UUID.UUIDString isEqualToString:kDeviceInfoUUID]) {\r
+            //设备信息特征\r
+            self.deviceInfoCha = characteristic;\r
+            [peripheral setNotifyValue:YES forCharacteristic:characteristic];\r
+            \r
+            //切换代理\r
+            [AiMouse shared].delegate = self;\r
+            //切换当前连接类型\r
+            [[AiMouse shared] switchConnect:AiMouseConnectType_bluetooth];\r
+\r
+        }\r
+    }\r
+}\r
+//收到蓝牙数据\r
+- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {\r
+    NSData *data = characteristic.value;\r
+    if([characteristic.UUID.UUIDString isEqualToString:kAiUUID]){\r
+        //AI数据\r
+        [[AiMouse shared] sendAiDataToSdk:data];\r
+    }\r
+    else if([characteristic.UUID.UUIDString isEqualToString:kDeviceInfoUUID]){\r
+        //设备信息数据\r
+        [[AiMouse shared] sendDeviceDataToSdk:data];\r
+    }\r
+}\r
+\r
+#pragma mark - AiDelegate\r
+/// 设备鉴权结果返回\r
+/// - Parameter authState: 鉴权状态\r
+-(void)aiMouseAuthenticationResult:(AiMouseAuthState)authState{\r
+    [self showLog:[NSString stringWithFormat:@"鉴权结果 = %@",@(authState)]];\r
+    if(self.authCallBack){\r
+        self.authCallBack(authState == AiMouseAuthState_Success);\r
+    }\r
+}\r
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备\r
+/// - Parameter data: 数据\r
+-(void)aiMouseSendDeviceInfoData:(NSData *)data{\r
+//    [self showLog:[NSString stringWithFormat:@"[send] %@",data]];\r
+    [self.connectPeripheral writeValue:data forCharacteristic:self.deviceInfoCha type:CBCharacteristicWriteWithoutResponse];\r
+}\r
+\r
+/// 鼠标按键指令回调\r
+/// - Parameter data: 数据。\r
+/// 前  第一次单击31(开始录音), 第二次单击32(结束录音) , 按下21(开始录音)抬起22(结束录音)\r
+/// 中  单击42, 按下25(开始录音)抬起26(结束录音)\r
+/// 后  单击43,按下27(开始录音)抬起28(结束录音)\r
+/// 其他未定义\r
+- (void)aiMouseSendCmdData:(NSData *)data{\r
+    uint8 cmd;\r
+    [data getBytes:&cmd length:1];\r
+    [self showLog:[NSString stringWithFormat:@"收到指令 = %X",cmd]];\r
+    \r
+    if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){\r
+        [self showLog:@"开始录音"];\r
+        self.recordData = [[NSMutableData alloc] init];\r
+    }\r
+    else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){\r
+        [self showLog:@"结束录音"];\r
+        [self finishRecored:self.recordData];\r
+    }\r
+}\r
+\r
+/// 录音回调\r
+/// - Parameter data: pcm格式语音数据\r
+- (void)aiMouseSendRecordData:(NSData *)data{\r
+    [self.recordData appendData:data];\r
+}\r
+/// 输出日志\r
+- (void)aiMouseAddLog:(NSString *)log { \r
+//    [self showLog:log];\r
+}\r
+\r
+// 连接状态改变\r
+- (void)aiMouseConnectStateDidChange:(BOOL)connected{\r
+    if (connected) {\r
+        [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidConnectBleNotification object:nil];\r
+    }\r
+    else{\r
+        [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidDisconnectBleNotification object:nil];\r
+    }\r
+}\r
+#pragma mark - public\r
+/// 连接设备\r
+- (void)startScanDevice{\r
+    [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO]}];\r
+}\r
+\r
+/// 扫描设备\r
+- (void)stopScanDevice{\r
+    [self.centralManager stopScan];\r
+}\r
+\r
+/// 连接设备\r
+- (void)connectPeripheral:(CBPeripheral *)peripheral{\r
+    self.connectPeripheral = peripheral;\r
+    [self.centralManager connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey : [NSNumber numberWithBool:YES]}];\r
+}\r
+\r
+/// 获取设备信息\r
+- (void)getDeviceInfo:(void (^)(AiMouseDeviceInfo * _Nonnull, NSError * _Nonnull))completation{\r
+    [[AiMouse shared] getDeviceInfo:completation];\r
+}\r
+\r
+#pragma mark - other\r
+//自动连接,获取已配对的蓝牙,取第一个连接\r
+- (void)autoConnectDevice{\r
+    //如果是已连接或者正在连接,则不再重连\r
+    if (self.connectPeripheral && (self.connectPeripheral.state == CBPeripheralStateConnected || self.connectPeripheral.state == CBPeripheralStateConnecting)) {\r
+        return;\r
+    }\r
+    NSArray<CBPeripheral *> *devices = [self.centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]]];\r
+    if (devices.count > 0) {\r
+        CBPeripheral *peripheral = devices[0];\r
+        [self showLog:[NSString stringWithFormat:@"匹配过的设备%@",peripheral.name]];\r
+        [self connectPeripheral:peripheral];\r
+    }\r
+    // 10s自动重连\r
+    [self performSelector:@selector(autoConnectDevice) withObject:nil afterDelay:10];\r
+}\r
+- (void)showLog:(NSString *)log{\r
+    NSLog(@"%@",log);\r
+}\r
+\r
+//将录音数据保存\r
+- (void)finishRecored:(NSData *)data{\r
+    NSString *doc = NSTemporaryDirectory();\r
+    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc];\r
+    NSError *err;\r
+    [data writeToFile:fullPath options:NSDataWritingAtomic error:&err];\r
+    if (err) {\r
+        [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]];\r
+    }\r
+    else{\r
+        [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]];\r
+    }\r
+}\r
+\r
+- (NSString *)dataToHexString:(NSData *)data {\r
+    NSUInteger length = data.length;\r
+    NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];\r
+    const unsigned char *byte = data.bytes;\r
+    for (int i = 0; i < length; i++, byte++) {\r
+        [result appendFormat:@"%02X", *byte];\r
+    }\r
+    return result;\r
+}\r
+@end\r
+\r
+```\r
+\r
+### usb示例\r
+\r
+```objective-c\r
+//\r
+//  HidManager.m\r
+//  MouseSdk\r
+//\r
+//  Created by incar on 2024/3/26.\r
+//\r
+\r
+#import "HidManager.h"\r
+#include <IOKit/hid/IOHIDLib.h>\r
+#include <IOKit/hid/IOHIDKeys.h>\r
+#import "Config.h"\r
+#import "DeviceManger.h"\r
+\r
+//供应商id\r
+const long vendorID = 0x6675;\r
+//产品id\r
+const long productID = 0x8601;\r
+\r
+@interface HidManager ()<AiMouseDelegate>\r
+\r
+@property(nonatomic, assign) IOHIDManagerRef hidManager;///hid管理\r
+@property(nonatomic, assign) IOHIDDeviceRef aiCha;//ai通道\r
+@property(nonatomic, assign) IOHIDDeviceRef deviceInfoCha;//设备信息通道\r
+@property (nonatomic,strong) NSMutableData *recordData;//录音的数据\r
+@end\r
+\r
+@implementation HidManager\r
+\r
+- (instancetype)init{\r
+    self = [super init];\r
+    if (self) {\r
+        [self initHid];\r
+        [self open];\r
+    }\r
+    return self;\r
+}\r
+\r
+- (void)initHid{\r
+\r
+}\r
+\r
+- (void)open{\r
+    self.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);\r
+    IOHIDManagerScheduleWithRunLoop(self.hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);\r
+    IOReturn ret = IOHIDManagerOpen(self.hidManager, 0L);\r
+    if (ret != kIOReturnSuccess) {\r
+        NSLog(@"打开设备失败!");\r
+    }else{\r
+        NSLog(@"打开设备成功!");\r
+    }\r
+    //设置匹配设备\r
+    NSMutableDictionary* dict= [NSMutableDictionary dictionary];\r
+    [dict setValue:[NSNumber numberWithLong:productID] forKey:[NSString stringWithCString:kIOHIDProductIDKey encoding:NSUTF8StringEncoding]];\r
+    [dict setValue:[NSNumber numberWithLong:vendorID] forKey:[NSString stringWithCString:kIOHIDVendorIDKey encoding:NSUTF8StringEncoding]];\r
+    IOHIDManagerSetDeviceMatching(self.hidManager, (__bridge CFMutableDictionaryRef)dict);\r
+    //设置匹配到设备回调\r
+    IOHIDManagerRegisterDeviceMatchingCallback(self.hidManager, &hidAppearDevice, NULL);\r
+    //设置移除设备回调\r
+    IOHIDManagerRegisterDeviceRemovalCallback(self.hidManager, &hidDisAppearDevice, NULL);\r
+}\r
+\r
+- (void)close{\r
+    IOReturn ret = IOHIDManagerClose(self.hidManager, 0);\r
+    if (ret != kIOReturnSuccess) {\r
+        NSLog(@"关闭设备失败!");\r
+    }else{\r
+        NSLog(@"关闭设备成功!");\r
+    }\r
+}\r
+\r
+#pragma mark - Hid CallBack\r
+/// 匹配到设备回调\r
+static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {\r
+    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"发现设备 %@\n ",inIOHIDDeviceRef]];\r
+    uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey));\r
+    uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey));\r
+    if(PrimaryUsagePage == 1 && PrimaryUsage == 0){\r
+        //设备信息\r
+        [DeviceManger shared].hid.deviceInfoCha = inIOHIDDeviceRef;\r
+        [[DeviceManger shared].hid connectDevice];\r
+        //注册消息回调\r
+        unsigned char InputBuffer[128];\r
+        IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL);\r
+        IOReturn ret = IOHIDDeviceOpen(inIOHIDDeviceRef, 0);\r
+        if (ret != kIOReturnSuccess) {\r
+            NSLog(@"打开设备信息失败!");\r
+        }else{\r
+            NSLog(@"打开设备信息成功!");\r
+        }\r
+        CFRetain(inIOHIDDeviceRef);\r
+        IOHIDDeviceScheduleWithRunLoop(inIOHIDDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);\r
+    }\r
+    else if(PrimaryUsagePage == 65283 && PrimaryUsage == 0){\r
+        //ai\r
+        [DeviceManger shared].hid.aiCha = inIOHIDDeviceRef;\r
+        //注册消息回调\r
+        unsigned char InputBuffer[128];\r
+        IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL);\r
+        IOReturn ret = IOHIDDeviceOpen(inIOHIDDeviceRef, 0);\r
+        if (ret != kIOReturnSuccess) {\r
+            NSLog(@"打开AI失败!");\r
+        }else{\r
+            NSLog(@"打开AI成功!");\r
+        }\r
+        CFRetain(inIOHIDDeviceRef);\r
+        IOHIDDeviceScheduleWithRunLoop(inIOHIDDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);\r
+    }\r
+}\r
+\r
+/// 设备移除回调\r
+static void hidDisAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {\r
+    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"拔出设备 %@ ",inIOHIDDeviceRef]];\r
+    uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey));\r
+    uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey));\r
+\r
+    if(PrimaryUsagePage == 1 && PrimaryUsage == 0){\r
+        [DeviceManger shared].hid.deviceInfoCha = nil;\r
+        [DeviceManger shared].hid.aiCha = nil;\r
+        [[AiMouse shared] didDisconnect];\r
+    }\r
+}\r
+\r
+static void hidReceiveData(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength){\r
+//    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"[receive] 处理前 %@",[AiMouseTools dataToHexString:[[NSData alloc] initWithBytes:report length:reportLength]]]];\r
+    \r
+    NSInteger len = get_data_len(report, reportLength);\r
+    NSData *data = [[NSData alloc] initWithBytes:report length:len];\r
+//    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"[receive] 处理后 %@",[AiMouseTools dataToHexString:data]]];\r
+    \r
+    uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsagePageKey));\r
+    uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsageKey));\r
+\r
+    if(PrimaryUsagePage == 1 && PrimaryUsage == 0){\r
+        [[AiMouse shared] sendDeviceDataToSdk:data];\r
+        \r
+    }\r
+    else if(PrimaryUsagePage == 65283 && PrimaryUsage == 0){\r
+        [[AiMouse shared] sendAiDataToSdk:data];\r
+    }\r
+}\r
+#pragma mark - AiDelegate\r
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备\r
+/// - Parameter data: 数据\r
+-(void)aiMouseSendDeviceInfoData:(NSData *)data{\r
+    [self sendData:data toDevice:self.deviceInfoCha];\r
+}\r
+\r
+/// 设备鉴权结果返回\r
+/// - Parameter authState: 鉴权状态\r
+-(void)aiMouseAuthenticationResult:(AiMouseAuthState)authState{\r
+    [self showLog:[NSString stringWithFormat:@"鉴权结果 = %@",@(authState)]];\r
+    if(self.authCallBack){\r
+        self.authCallBack(authState == AiMouseAuthState_Success);\r
+    }\r
+}\r
+/// 鼠标按键指令回调\r
+/// - Parameter data: 数据。\r
+/// 前  第一次单击31(开始录音), 第二次单击32(结束录音) , 按下21(开始录音)抬起22(结束录音)\r
+/// 中  单击42, 按下25(开始录音)抬起26(结束录音)\r
+/// 后  单击43,按下27(开始录音)抬起28(结束录音)\r
+/// 其他未定义\r
+- (void)aiMouseSendCmdData:(NSData *)data{\r
+    uint8 cmd;\r
+    [data getBytes:&cmd length:1];\r
+    [self showLog:[NSString stringWithFormat:@"收到指令 = %X",cmd]];\r
+    \r
+    if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){\r
+        [self showLog:@"开始录音"];\r
+        self.recordData = [[NSMutableData alloc] init];\r
+    }\r
+    else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){\r
+        [self showLog:@"结束录音"];\r
+        [self finishRecored:self.recordData];\r
+    }\r
+}\r
+/// 录音回调\r
+/// - Parameter data: pcm格式语音数据\r
+- (void)aiMouseSendRecordData:(NSData *)data{\r
+    [self.recordData appendData:data];\r
+}\r
+/// 输出日志\r
+- (void)aiMouseAddLog:(NSString *)log {\r
+//    [self showLog:log];\r
+}\r
+\r
+-(void)hidDataError{\r
+    [self close];\r
+    [self open];\r
+}\r
+\r
+// 连接状态改变\r
+- (void)aiMouseConnectStateDidChange:(BOOL)connected{\r
+    [DeviceManger shared].hid.connectStateCallBack(connected);\r
+}\r
+#pragma mark - public\r
+-(void)connectDevice{\r
+    //设置代理\r
+    [AiMouse shared].delegate = self;\r
+    //切换当前连接类型\r
+    [[AiMouse shared] switchConnect:AiMouseConnectType_hid];\r
+}\r
+/// 获取设备信息\r
+- (void)getDeviceInfo:(void (^)(AiMouseDeviceInfo * _Nonnull, NSError * _Nonnull))completation{\r
+    [[AiMouse shared] getDeviceInfo:completation];\r
+}\r
+\r
+/// 发送数据给设备\r
+-(void)sendData:(NSData *)data toDevice:(IOHIDDeviceRef)device{\r
+    if(device == nil){\r
+        [self showLog:@"请先连上设备"];\r
+        return;\r
+    }\r
+    \r
+    IOReturn ret = IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, 0, data.bytes, data.length);\r
+    if (ret != kIOReturnSuccess) {\r
+        [self showLog:[NSString stringWithFormat:@"数据发送失败! code = %d",ret]];\r
+    }else{\r
+        [self showLog:@"数据发送成功!"];\r
+    }\r
+}\r
+//将录音数据保存\r
+- (void)finishRecored:(NSData *)data{\r
+    NSString *doc = NSTemporaryDirectory();\r
+    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc];\r
+    NSError *err;\r
+    [data writeToFile:fullPath options:NSDataWritingAtomic error:&err];\r
+    if (err) {\r
+        [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]];\r
+    }\r
+    else{\r
+        [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]];\r
+    }\r
+}\r
+- (void)showLog:(NSString *)log{\r
+    NSLog(@"%@",log);\r
+}\r
+\r
+/// 实际到数据长度\r
+static inline int get_data_len(uint8_t *buf, u_long len) {\r
+    if (len <= 0 ) return 0;\r
+    int url_len = 0;\r
+    while (buf[url_len] != 0x00) url_len++;\r
+    return url_len;\r
+}\r
+\r
+/// 获取对应属性值\r
+static uint32_t IOHIDDevGetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey) {\r
+    uint32_t outValue = 0;\r
+    if (inIOHIDDeviceRef != NULL) {\r
+        CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey);\r
+        if (tCFTypeRef != NULL && (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef))  ) {\r
+            Boolean result = CFNumberGetValue((CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, &outValue);\r
+            if (!result) {\r
+                [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"get %@ failed.", inKey]];\r
+            }\r
+        }\r
+    }\r
+    return outValue;\r
+}\r
+@end\r
+\r
+```\r
+\r
 \r
 \r
 ## 注意事项\r
 \r
-- 设备信息数据使用的是设备信息通道,走设备信息接口;AI数据使用的是AI通道,走AI接口。\r
-- 只有鉴权成功了以后,AI相关功能才可用。\r
+- 不同的方案,使用不同的通讯方式\r
 \r
index f404dbd..bea1301 100644 (file)
Binary files "a/Document/AiMouseSdk\346\216\245\345\217\243\346\226\207\346\241\243.pdf" and "b/Document/AiMouseSdk\346\216\245\345\217\243\346\226\207\346\241\243.pdf" differ
index d3b5733..3c2c2c2 100644 (file)
@@ -3,7 +3,7 @@
        archiveVersion = 1;
        classes = {
        };
-       objectVersion = 56;
+       objectVersion = 55;
        objects = {
 
 /* Begin PBXBuildFile section */
                18AD83262BFAE8B9009BDE3B /* AiDeviceInfoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18AD83242BFAE8B9009BDE3B /* AiDeviceInfoManager.m */; };
                18AD83292BFB22DE009BDE3B /* AiSender.h in Headers */ = {isa = PBXBuildFile; fileRef = 18AD83272BFB22DE009BDE3B /* AiSender.h */; };
                18AD832A2BFB22DE009BDE3B /* AiSender.m in Sources */ = {isa = PBXBuildFile; fileRef = 18AD83282BFB22DE009BDE3B /* AiSender.m */; };
+               18EF7AF72C7603E800AC5223 /* TlwRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AE82C7603E800AC5223 /* TlwRecordManager.m */; };
+               18EF7AF82C7603E800AC5223 /* ADPCMConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AE92C7603E800AC5223 /* ADPCMConverter.m */; };
+               18EF7AF92C7603E800AC5223 /* adpcm.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AEA2C7603E800AC5223 /* adpcm.h */; };
+               18EF7AFA2C7603E800AC5223 /* TlwMouseSdk.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AEB2C7603E800AC5223 /* TlwMouseSdk.m */; };
+               18EF7AFB2C7603E800AC5223 /* TlwTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AEC2C7603E800AC5223 /* TlwTools.m */; };
+               18EF7AFC2C7603E800AC5223 /* TlwConnectManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AED2C7603E800AC5223 /* TlwConnectManager.m */; };
+               18EF7AFD2C7603E800AC5223 /* TlwAudioManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AEE2C7603E800AC5223 /* TlwAudioManager.h */; };
+               18EF7AFE2C7603E800AC5223 /* ADPCMConverter.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AEF2C7603E800AC5223 /* ADPCMConverter.h */; };
+               18EF7B002C7603E800AC5223 /* TlwRecordManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AF12C7603E800AC5223 /* TlwRecordManager.h */; };
+               18EF7B012C7603E800AC5223 /* adpcm.c in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AF22C7603E800AC5223 /* adpcm.c */; };
+               18EF7B022C7603E800AC5223 /* TlwTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AF32C7603E800AC5223 /* TlwTools.h */; };
+               18EF7B032C7603E800AC5223 /* TlwMouseSdk.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AF42C7603E800AC5223 /* TlwMouseSdk.h */; settings = {ATTRIBUTES = (Public, ); }; };
+               18EF7B042C7603E800AC5223 /* TlwAudioManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7AF52C7603E800AC5223 /* TlwAudioManager.m */; };
+               18EF7B052C7603E800AC5223 /* TlwConnectManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 18EF7AF62C7603E800AC5223 /* TlwConnectManager.h */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
                18AD83242BFAE8B9009BDE3B /* AiDeviceInfoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiDeviceInfoManager.m; sourceTree = "<group>"; };
                18AD83272BFB22DE009BDE3B /* AiSender.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AiSender.h; sourceTree = "<group>"; };
                18AD83282BFB22DE009BDE3B /* AiSender.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AiSender.m; sourceTree = "<group>"; };
+               18EF7AE82C7603E800AC5223 /* TlwRecordManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwRecordManager.m; sourceTree = "<group>"; };
+               18EF7AE92C7603E800AC5223 /* ADPCMConverter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADPCMConverter.m; sourceTree = "<group>"; };
+               18EF7AEA2C7603E800AC5223 /* adpcm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = adpcm.h; sourceTree = "<group>"; };
+               18EF7AEB2C7603E800AC5223 /* TlwMouseSdk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwMouseSdk.m; sourceTree = "<group>"; };
+               18EF7AEC2C7603E800AC5223 /* TlwTools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwTools.m; sourceTree = "<group>"; };
+               18EF7AED2C7603E800AC5223 /* TlwConnectManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwConnectManager.m; sourceTree = "<group>"; };
+               18EF7AEE2C7603E800AC5223 /* TlwAudioManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwAudioManager.h; sourceTree = "<group>"; };
+               18EF7AEF2C7603E800AC5223 /* ADPCMConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADPCMConverter.h; sourceTree = "<group>"; };
+               18EF7AF12C7603E800AC5223 /* TlwRecordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwRecordManager.h; sourceTree = "<group>"; };
+               18EF7AF22C7603E800AC5223 /* adpcm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = adpcm.c; sourceTree = "<group>"; };
+               18EF7AF32C7603E800AC5223 /* TlwTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwTools.h; sourceTree = "<group>"; };
+               18EF7AF42C7603E800AC5223 /* TlwMouseSdk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwMouseSdk.h; sourceTree = "<group>"; };
+               18EF7AF52C7603E800AC5223 /* TlwAudioManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwAudioManager.m; sourceTree = "<group>"; };
+               18EF7AF62C7603E800AC5223 /* TlwConnectManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwConnectManager.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
                180E7A9D2BD7BB12008558E8 = {
                        isa = PBXGroup;
                        children = (
+                               18EF7AE72C7603E800AC5223 /* TlwMouseSdk */,
                                180E7AA82BD7BB12008558E8 /* AiMouseSdk */,
                                180E7AA72BD7BB12008558E8 /* Products */,
                                180E7D092BD8FEB2008558E8 /* Frameworks */,
                        path = AFNetworking;
                        sourceTree = "<group>";
                };
+               18EF7AE72C7603E800AC5223 /* TlwMouseSdk */ = {
+                       isa = PBXGroup;
+                       children = (
+                               18EF7AF42C7603E800AC5223 /* TlwMouseSdk.h */,
+                               18EF7AEB2C7603E800AC5223 /* TlwMouseSdk.m */,
+                               18EF7AF12C7603E800AC5223 /* TlwRecordManager.h */,
+                               18EF7AE82C7603E800AC5223 /* TlwRecordManager.m */,
+                               18EF7AF32C7603E800AC5223 /* TlwTools.h */,
+                               18EF7AEC2C7603E800AC5223 /* TlwTools.m */,
+                               18EF7AF62C7603E800AC5223 /* TlwConnectManager.h */,
+                               18EF7AED2C7603E800AC5223 /* TlwConnectManager.m */,
+                               18EF7AEE2C7603E800AC5223 /* TlwAudioManager.h */,
+                               18EF7AF52C7603E800AC5223 /* TlwAudioManager.m */,
+                               18EF7AEF2C7603E800AC5223 /* ADPCMConverter.h */,
+                               18EF7AE92C7603E800AC5223 /* ADPCMConverter.m */,
+                               18EF7AEA2C7603E800AC5223 /* adpcm.h */,
+                               18EF7AF22C7603E800AC5223 /* adpcm.c */,
+                       );
+                       path = TlwMouseSdk;
+                       sourceTree = "<group>";
+               };
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
                                180E7C882BD8FC4E008558E8 /* hwcontext.h in Headers */,
                                180E7CB12BD8FC4E008558E8 /* dovi_meta.h in Headers */,
                                184A710F2BDA4C050025A332 /* AFHTTPSessionManager.h in Headers */,
+                               18EF7B052C7603E800AC5223 /* TlwConnectManager.h in Headers */,
                                180E7CF92BD8FC4F008558E8 /* dirac.h in Headers */,
                                184A71032BDA4C050025A332 /* AFNetworkReachabilityManager.h in Headers */,
                                184A71242BDE2DA60025A332 /* AiSecretModel.h in Headers */,
                                180E7CBA2BD8FC4E008558E8 /* common.h in Headers */,
                                180E7C9D2BD8FC4E008558E8 /* avutil.h in Headers */,
                                180E7C982BD8FC4E008558E8 /* rational.h in Headers */,
+                               18EF7B022C7603E800AC5223 /* TlwTools.h in Headers */,
                                180E7CF82BD8FC4F008558E8 /* codec_id.h in Headers */,
                                180E7C9B2BD8FC4E008558E8 /* avconfig.h in Headers */,
                                180E7CDA2BD8FC4E008558E8 /* avassert.h in Headers */,
                                180E7CD92BD8FC4E008558E8 /* downmix_info.h in Headers */,
                                180E7CA72BD8FC4E008558E8 /* spherical.h in Headers */,
                                180E7CA92BD8FC4E008558E8 /* hwcontext_opencl.h in Headers */,
+                               18EF7B032C7603E800AC5223 /* TlwMouseSdk.h in Headers */,
                                180E7B542BD7BD83008558E8 /* AiFrameManager.h in Headers */,
                                180E7CD12BD8FC4E008558E8 /* parseutils.h in Headers */,
                                180E7CEC2BD8FC4E008558E8 /* codec_par.h in Headers */,
                                180E7CB92BD8FC4E008558E8 /* hwcontext_drm.h in Headers */,
                                180E7CE22BD8FC4E008558E8 /* version.h in Headers */,
                                189C15862C05726600676A80 /* AiConnectManager.h in Headers */,
+                               18EF7AFD2C7603E800AC5223 /* TlwAudioManager.h in Headers */,
                                180E7C892BD8FC4E008558E8 /* time.h in Headers */,
                                180E7CD32BD8FC4E008558E8 /* bprint.h in Headers */,
                                180E7CB02BD8FC4E008558E8 /* timecode.h in Headers */,
                                180E7CBB2BD8FC4E008558E8 /* hmac.h in Headers */,
                                184A710B2BDA4C050025A332 /* AFNetworking.h in Headers */,
                                180E7CAC2BD8FC4E008558E8 /* threadmessage.h in Headers */,
+                               18EF7AF92C7603E800AC5223 /* adpcm.h in Headers */,
                                180E7B582BD7BD83008558E8 /* AiRecordManager.h in Headers */,
                                180E7CE12BD8FC4E008558E8 /* avdevice.h in Headers */,
                                180E7CE92BD8FC4E008558E8 /* defs.h in Headers */,
+                               18EF7AFE2C7603E800AC5223 /* ADPCMConverter.h in Headers */,
                                180E7C942BD8FC4E008558E8 /* stereo3d.h in Headers */,
                                180E7CDE2BD8FC4E008558E8 /* film_grain_params.h in Headers */,
                                180E7C8C2BD8FC4E008558E8 /* error.h in Headers */,
                                180E7CD52BD8FC4E008558E8 /* hwcontext_qsv.h in Headers */,
                                180E7CD72BD8FC4E008558E8 /* aes_ctr.h in Headers */,
                                180E7CFC2BD8FC4F008558E8 /* vorbis_parser.h in Headers */,
+                               18EF7B002C7603E800AC5223 /* TlwRecordManager.h in Headers */,
                                180E7CE82BD8FC4E008558E8 /* avcodec.h in Headers */,
                                180E7C962BD8FC4E008558E8 /* pixdesc.h in Headers */,
                        );
                                };
                        };
                        buildConfigurationList = 180E7AA12BD7BB12008558E8 /* Build configuration list for PBXProject "AiMouseSdk" */;
-                       compatibilityVersion = "Xcode 14.0";
+                       compatibilityVersion = "Xcode 13.0";
                        developmentRegion = en;
                        hasScannedForEncodings = 0;
                        knownRegions = (
                                184A71062BDA4C050025A332 /* AFURLResponseSerialization.m in Sources */,
                                184A710C2BDA4C050025A332 /* AFNetworkReachabilityManager.m in Sources */,
                                180E7BE42BD7BD84008558E8 /* AiRecordManager.m in Sources */,
+                               18EF7B042C7603E800AC5223 /* TlwAudioManager.m in Sources */,
+                               18EF7AF72C7603E800AC5223 /* TlwRecordManager.m in Sources */,
                                180E7BE32BD7BD84008558E8 /* AiFrameManager.m in Sources */,
                                180E7BE72BD7BD84008558E8 /* AiAudioManager.m in Sources */,
                                189C15872C05726600676A80 /* AiConnectManager.m in Sources */,
+                               18EF7AF82C7603E800AC5223 /* ADPCMConverter.m in Sources */,
+                               18EF7AFB2C7603E800AC5223 /* TlwTools.m in Sources */,
+                               18EF7AFA2C7603E800AC5223 /* TlwMouseSdk.m in Sources */,
                                180E7BDF2BD7BD84008558E8 /* AiMouseConfig.m in Sources */,
+                               18EF7AFC2C7603E800AC5223 /* TlwConnectManager.m in Sources */,
                                181CE5C22C6EED9900EF8D07 /* AiAliAuthManager.m in Sources */,
                                184A710D2BDA4C050025A332 /* AFSecurityPolicy.m in Sources */,
                                180E7B552BD7BD83008558E8 /* AiAudioDecoder.m in Sources */,
                                18AD83262BFAE8B9009BDE3B /* AiDeviceInfoManager.m in Sources */,
+                               18EF7B012C7603E800AC5223 /* adpcm.c in Sources */,
                                184A71092BDA4C050025A332 /* AFURLSessionManager.m in Sources */,
                                180E7BE82BD7BD84008558E8 /* AiAuthManager.m in Sources */,
                        );
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.h
new file mode 100644 (file)
index 0000000..0558f1e
--- /dev/null
@@ -0,0 +1,18 @@
+//
+//  AiAdpcm2pcm.h
+//  AiMouseSdk
+//
+//  Created by incar on 2024/8/20.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ADPCMConverter : NSObject
+
+- (NSData *)convertADPCMToPCM:(NSData *)adpcmData;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/ADPCMConverter.m
new file mode 100644 (file)
index 0000000..42e25f3
--- /dev/null
@@ -0,0 +1,92 @@
+
+#import "ADPCMConverter.h"
+#import <Foundation/Foundation.h>
+
+
+
+@implementation ADPCMConverter
+
+static const signed char idxtbl[] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8};
+static const unsigned short steptbl[] = {
+    7,  8,  9,  10,  11,  12,  13,  14,  16,  17,
+    19,  21,  23,  25,  28,  31,  34,  37,  41,  45,
+    50,  55,  60,  66,  73,  80,  88,  97,  107, 118,
+    130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+    337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+    876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+    2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+    5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+       15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+#define NUM_OF_ORIG_SAMPLE 2
+
+- (NSData *)convertADPCMToPCM:(NSData *)adpcmData {
+    NSMutableData *pcmData = [NSMutableData data];
+    const signed short *source = (const signed short *)[adpcmData bytes];
+    signed short *destination = (signed short *)[pcmData mutableBytes];
+    int len = (int)[adpcmData length] / sizeof(signed short);
+
+    int i;
+    // 假设 source 中的前两个元素分别是 predict 和 predict_idx
+    int predict = source[0];
+    int predict_idx = source[1] & 0xff;
+    const unsigned char *pcode = (const unsigned char *)(source + NUM_OF_ORIG_SAMPLE);
+    unsigned char code;
+    code = *pcode++;
+
+    for (i = 0; i < len; i++) {
+        if (1) {
+            int step = steptbl[predict_idx];
+
+            int diffq = step >> 3;
+
+            if (code & 4) {
+                diffq = diffq + step;
+            }
+            step = step >> 1;
+            if (code & 2) {
+                diffq = diffq + step;
+            }
+            step = step >> 1;
+            if (code & 1) {
+                diffq = diffq + step;
+            }
+
+            if (code & 8) {
+                predict = predict - diffq;
+            } else {
+                predict = predict + diffq;
+            }
+
+            if (predict > 32767) {
+                predict = 32767;
+            } else if (predict < -32768) {
+                predict = -32768;
+            }
+
+            predict_idx = predict_idx + idxtbl[code & 15];
+
+            if (predict_idx < 0) {
+                predict_idx = 0;
+            } else if (predict_idx > 88) {
+                predict_idx = 88;
+            }
+
+            if (i & 1) {
+                code = *pcode++;
+            } else {
+                code = code >> 4;
+            }
+        }
+
+        if (0 && i < NUM_OF_ORIG_SAMPLE) {
+            destination[i] = source[i];
+        } else {
+            destination[i] = predict;
+        }
+    }
+    return pcmData;
+}
+
+@end
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.h
new file mode 100644 (file)
index 0000000..bcbc620
--- /dev/null
@@ -0,0 +1,20 @@
+//
+//  TlwAudioManager.h
+//  AiMouse
+//
+//  Created by incar on 2024/2/28.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwAudioManager : NSObject
+@property (nonatomic,strong)void (^outputData)(NSData *data);
+@property (nonatomic,strong)void (^decodeCallBack)(NSData *data);
+@property (nonatomic,strong)void (^outputLog)(NSString *log);
+
+- (void)receiveData:(NSData *)data;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwAudioManager.m
new file mode 100644 (file)
index 0000000..460f163
--- /dev/null
@@ -0,0 +1,50 @@
+//
+//  TlwAudioManager.m
+//  AiMouse
+//
+//  Created by incar on 2024/2/28.
+//
+
+#import "TlwAudioManager.h"
+#import <AVFoundation/AVFoundation.h>
+#import "ADPCMConverter.h"
+#import "adpcm.h"
+
+@interface TlwAudioManager ()
+@end
+
+@implementation TlwAudioManager
+
+-(instancetype)init{
+    if (self = [super init]) {
+
+    }
+    return self;
+}
+
+- (void)receiveData:(NSData *)data{
+//    [self.audioDecoder decodeSBCData:data];
+    
+    NSData *pcmData = [self convertADPCMDataToPCMData:data];
+    if(self.decodeCallBack)self.decodeCallBack(pcmData);
+}
+- (NSData *)convertADPCMDataToPCMData:(NSData *)adpcmData {
+    const unsigned char *adpcmBytes = (const unsigned char *)[adpcmData bytes];
+    NSUInteger adpcmLength = [adpcmData length];
+
+    signed short *ps = (signed short *)malloc(adpcmLength);
+    memcpy(ps, adpcmBytes, adpcmLength);
+
+    signed short *pd = (signed short *)malloc(adpcmLength * 4);
+
+    adpcm_to_pcm(ps, pd, adpcmLength / sizeof(signed short)*2);
+
+    NSData *pcmData = [NSData dataWithBytes:pd length:adpcmLength * 4];
+
+    free(ps);
+    free(pd);
+
+    return pcmData;
+}
+
+@end
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.h
new file mode 100644 (file)
index 0000000..8a76ef8
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  TlwConnectManager.h
+//  AiMouseSdk
+//
+//  Created by incar on 2024/5/28.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+@class TlwConnectManager;
+@protocol TlwConnectManagerDelegate <NSObject>
+
+- (void)TlwConnectManager:(TlwConnectManager *)manager connectStateDidChange:(BOOL)connected;
+
+- (void)TlwConnectManagerReSendData:(TlwConnectManager *)manager;
+
+- (void)TlwConnectManager:(TlwConnectManager *)manager outputLog:(NSString *)log;
+
+@end
+@interface TlwConnectManager : NSObject
+@property (nonatomic, weak) id<TlwConnectManagerDelegate> delegate;
+
+- (void)start;
+
+//重置
+- (void)resetTimeOut;
+
+/// 增加超时
+- (void)addTimeOut;
+
+- (void)disConnected;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwConnectManager.m
new file mode 100644 (file)
index 0000000..e81e7ce
--- /dev/null
@@ -0,0 +1,94 @@
+//
+//  TlwConnectManager.m
+//  AiMouseSdk
+//
+//  Created by incar on 2024/5/28.
+//
+
+#import "TlwConnectManager.h"
+
+@interface TlwConnectManager()
+@property (nonatomic, assign) NSInteger currentValue;//当前计数
+@property (nonatomic, assign) NSInteger timeOut;//超时次数
+@property (nonatomic, strong) NSTimer *timer;
+@property (nonatomic, assign) int connecteState;//连接状态0未知,1连上,2断开连接
+@end
+
+@implementation TlwConnectManager
+-(instancetype)init{
+    if (self = [super init]) {
+
+    }
+    return self;
+}
+
+- (void)start{
+    self.timeOut = 0;
+    self.connecteState = 0;
+    
+    [self addSendTimer];
+}
+
+- (void)resetTimeOut{
+    self.timeOut = 0;
+    self.currentValue = 0;
+    
+    if (self.connecteState != 1) {
+        [self showLog:@"连上设备。。。"];
+        [self.delegate TlwConnectManager:self connectStateDidChange:true];
+    }
+    self.connecteState = 1;
+}
+
+- (void)addTimeOut{
+    self.timeOut++;
+    
+    if (self.timeOut >= 3) {
+        if (self.connecteState != 2) {
+            [self showLog:@"设备断开连接!!!"];
+            [self.delegate TlwConnectManager:self connectStateDidChange:false];
+        }
+        self.connecteState = 2;
+    }
+}
+
+- (void)disConnected{
+    [self showLog:@"设备断开连接!!!"];
+    [self.delegate TlwConnectManager:self connectStateDidChange:false];
+    self.connecteState = 2;
+}
+#pragma mark - timer
+- (void)addSendTimer{
+    dispatch_async(dispatch_queue_create("timeoutQueue", DISPATCH_QUEUE_SERIAL), ^{
+        self.timer = [NSTimer timerWithTimeInterval:1000 target:self selector:@selector(timerAction) userInfo:nil repeats:true];
+        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
+        [[NSRunLoop currentRunLoop] run];
+    });
+//    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:true];
+//    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
+}
+
+//- (void)removeSendTimer{
+//    if (self.timer && [self.timer isValid]) {
+//        [self.timer invalidate];
+//    }
+//}
+
+- (void)timerAction{
+    self.currentValue++;
+//    [self showLog:[NSString stringWithFormat:@"time = %ld",self.currentValue]];
+    if (self.currentValue >= 3) {
+        self.currentValue = 0;
+        if (self.delegate && [self.delegate respondsToSelector:@selector(TlwConnectManagerReSendData:)]) {
+//            [self showLog:@"检查是否断开连接"];
+            [self.delegate TlwConnectManagerReSendData:self];
+        }
+    }
+}
+
+- (void)showLog:(NSString *)log{
+    if (self.delegate && [self.delegate respondsToSelector:@selector(TlwConnectManager:outputLog:)]) {
+        [self.delegate TlwConnectManager:self outputLog:log];
+    }
+}
+@end
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.h
new file mode 100644 (file)
index 0000000..98376d5
--- /dev/null
@@ -0,0 +1,102 @@
+//
+//  AiMouseSdk.h
+//  AiMouseSdk
+//
+//  Created by incar on 2024/4/23.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface TlwDeviceInfo : NSObject
+@property (nonatomic, assign) int DPI;/// DPI值 数值1 ~ 4 (分别对应 800 1200 1800 2400)
+@property (nonatomic, assign) int version;///软件版本
+@property (nonatomic, assign) int batteryState;/// 电池状态
+@property (nonatomic, assign) int batteryValue;/// 电池百分百电量,0-100
+@end
+
+/// 日志等级
+typedef NS_ENUM(NSInteger,TlwMouseLogLevel) {
+    TlwMouseLogLevel_none = 0,
+    TlwMouseLogLevel_error,
+    TlwMouseLogLevel_info,
+};
+
+typedef NS_ENUM(NSInteger,TlwConnectType) {
+    TlwConnectType_bluetooth = 0,///蓝牙
+    TlwConnectType_hid,///hid
+};
+
+@protocol TlwMouseDelegate <NSObject>
+
+/// 设备连接状态
+/// - Parameter connected: 是否连接
+- (void)aiMouseConnectStateDidChange:(BOOL)connected;
+
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备
+/// - Parameter data: 数据
+- (void)tlwMouseSendDeviceInfoData:(NSData *)data;
+
+/// 鼠标按键指令回调
+/// - Parameter data: 数据。
+/// 前  单击0x06, 按下0x13(开始录音)抬起0x14(结束录音)
+/// 中  单击0x03, 按下0x11(开始录音)抬起0x12(结束录音)
+/// 后  第一次单击0x01, 第二次单击0x02(结束录音) , 按下0x09(开始录音)抬起0x10(结束录音)
+/// 其他未定义
+- (void)tlwMouseSendCmdData:(NSData *)data;
+
+/// 录音回调
+/// - Parameter data: pcm格式语音数据
+- (void)tlwMouseSendRecordData:(NSData *)data;
+
+@optional
+/// 输出sdk日志
+/// - Parameter log: 日志
+- (void)aiMouseAddLog:(NSString *)log;
+
+@end
+
+@interface TlwMouse : NSObject
+@property (nonatomic, weak) id<TlwMouseDelegate> delegate;/// 代理
+@property (nonatomic, readonly) TlwConnectType connectType;//连接类型
+@property (nonatomic, readonly) BOOL connected;//是否已连接
+
+/// 单利
++(TlwMouse *)shared;
+
+/// 设置日志等级
+/// - Parameter level: 等级
+- (void)setLogLevel:(TlwMouseLogLevel)level;
+
+/// 断开连接
+- (void)didDisconnect;
+
+/// 切换连接类型
+/// - Parameter type: 连接类型
+- (void)switchConnect:(TlwConnectType)type;
+
+/// 传输业务数据给sdk
+/// - Parameter data:ai特征传过来的数据
+- (void)sendAiDataToSdk:(NSData *)data;
+
+///  传输设备信息数据给sdk
+/// - Parameter data:设备信息特征传过来的数据
+- (void)sendDeviceDataToSdk:(NSData *)data;
+
+/// 获取设备信息
+/// - Parameter completation: 如果获取成功,则deviceInfo有值,否则为nil
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))complete;
+
+/// 设置DPI
+/// - Parameters:
+///   - value: 数值1 ~ 4 (分别对应 800 1200 1800 2400)
+///   - finished: 设置结果回调
+- (void)setDPI:(uint8_t)value completation:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 打开录音
+/// - finished: 执行结果回调
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 关闭录音
+/// - finished: 执行结果回调
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+@end
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwMouseSdk.m
new file mode 100644 (file)
index 0000000..3785506
--- /dev/null
@@ -0,0 +1,492 @@
+//
+//  AiMouseSdk.m
+//  AiMouseSdk
+//
+//  Created by incar on 2024/4/23.
+//
+
+#import "TlwMouseSdk.h"
+#import "TlwAudioManager.h"
+#import "TlwTools.h"
+#import "TlwRecordManager.h"
+#import "TlwConnectManager.h"
+
+@interface TlwDeviceInfo()
+@end
+@implementation TlwDeviceInfo
+-(NSString *)description{
+    NSString *info = [NSString stringWithFormat:@"\n version = %d\n DPI = %d\n batteryState = %d\n batteryValue = %d\n",self.version,self.DPI,self.batteryState,self.batteryValue];
+    return info;
+}
+@end
+
+@interface TlwMouse()<TlwConnectManagerDelegate>
+
+@property (nonatomic, assign) TlwMouseLogLevel logLevel;
+@property (nonatomic, strong) TlwAudioManager *audioManager;
+@property (nonatomic, strong) TlwRecordManager *recordManager;
+@property (nonatomic, strong) TlwConnectManager *connectManager;
+
+@property (nonatomic,strong)void (^openRecord)(BOOL isSuccess,NSError *error);
+@property (nonatomic,strong)void (^closeRecord)(BOOL isSuccess,NSError *error);
+@property (nonatomic,strong)void (^setDpi)(BOOL isSuccess,NSError *error);
+@property (nonatomic,strong)void (^getMouseInfo)(TlwDeviceInfo *deviceInfo,NSError *error);
+@end
+
+@implementation TlwMouse
++(TlwMouse *)shared{
+    static TlwMouse *manager = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        manager = [[TlwMouse alloc]init];
+    });
+    return manager;
+}
+
+-(instancetype)init{
+    if (self = [super init]) {
+        _logLevel = TlwMouseLogLevel_none;
+        _connectType = TlwConnectType_bluetooth;
+        [self.connectManager start];
+        NSLog(@"[TlwMouseSdk]%@",[NSString stringWithFormat:@"AiMouseSdk_v%@",self.version]);
+    }
+    return self;
+}
+
+-(NSString *)version{
+    return @"1.0.0";
+}
+
+-(TlwAudioManager *)audioManager{
+    if (!_audioManager) {
+        _audioManager = [[TlwAudioManager alloc] init];
+        __weak typeof(self) ws = self;
+        _audioManager.decodeCallBack = ^(NSData * _Nonnull data) {
+            [ws sendRecordData:data];
+        };
+        _audioManager.outputLog = ^(NSString * _Nonnull log) {
+            [ws showLog:log];
+        };
+    }
+    return _audioManager;
+}
+
+- (TlwRecordManager *)recordManager{
+    if (!_recordManager) {
+        _recordManager = [[TlwRecordManager alloc] init];
+        __weak typeof(self) ws = self;
+        _recordManager.didRecordData = ^(NSData * _Nonnull data) {
+            [ws showLog:[NSString stringWithFormat:@"%@",data]];
+            [ws sendRecordData:data];
+        };
+    }
+    return _recordManager;
+}
+
+-(TlwConnectManager *)connectManager{
+    if (!_connectManager) {
+        _connectManager = [[TlwConnectManager alloc] init];
+        _connectManager.delegate = self;
+    }
+    return _connectManager;
+}
+
+#pragma mark - pub Api
+- (void)setLogLevel:(TlwMouseLogLevel)level{
+    _logLevel = level;
+}
+- (void)didDisconnect{
+    [self showLog:@"系统断开连接"];
+    [self stopRecord];
+    [self.connectManager disConnected];
+}
+
+- (void)switchConnect:(TlwConnectType)type{
+    if(type != _connectType && self.connected){
+        [self.connectManager disConnected];
+    }
+    _connectType = type;
+    [self showLog:type == TlwConnectType_bluetooth?@"sdk切换到蓝牙模式":@"sdk切换到usb模式"];
+    
+
+    if (type == TlwConnectType_hid) {
+        [self.recordManager checkPermissions];
+    }
+}
+
+- (void)sendAiDataToSdk:(NSData *)data{
+    [self.connectManager resetTimeOut];
+    
+    [self showLog:[NSString stringWithFormat:@"[receive][Ai] %@",data]];
+    if(self.connectType == TlwConnectType_hid){
+////        0AFFFF12BC32
+//        if(data.length == 6){
+//            NSData *subData = [data subdataWithRange:NSMakeRange(5, 1)];
+//            [self sendCmdData:subData];
+//            uint8 cmd;
+//            [subData getBytes:&cmd length:1];
+////            [self showLog:[NSString stringWithFormat:@"cmd = %X",cmdInt]];
+//            if(cmd == 0x31 || cmd == 0x21 || cmd == 0x25 ||cmd == 0x27){
+//                self.recordManager.startCmd = subData;
+//                [self startRecord];
+//            }
+//            else if(cmd == 0x32 || cmd == 0x22 || cmd == 0x26 ||cmd == 0x28){
+//                [self stopRecord:false];
+//            }
+//        }
+//        else{
+//            [self showLog:@"未实现指令"];
+//        }
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        [self bleReceiveAiData:data];
+    }
+}
+
+-(void)sendDeviceDataToSdk:(NSData *)data{
+    [self showLog:[NSString stringWithFormat:@"[receive][info] %@",data]];
+    if(self.connectType == TlwConnectType_hid){
+        [self hidReceiveDeviceData:data];
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        [self bleReceiveDeviceData:data];
+    }
+}
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if(self.connectType == TlwConnectType_hid){
+        self.openRecord = finished;
+        uint8_t cmd = 0x03;
+        uint8_t len = 0x00;
+        uint8_t tail = 0xEF;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendData:[self getHidHeader]];
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&len length:1];
+        [mData appendBytes:&tail length:1];
+        [self sendToDevice:mData];
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        self.openRecord = finished;
+        uint8_t cmd = 0x03;
+        uint8_t dir = 0x01;
+        uint8_t len = 0x00;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&dir length:1];
+        [mData appendBytes:&len length:1];
+        [self sendToDevice:mData];
+    }
+}
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if(self.connectType == TlwConnectType_hid){
+        self.closeRecord = finished;
+        uint8_t cmd = 0x04;
+        uint8_t len = 0x00;
+        uint8_t tail = 0xEF;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendData:[self getHidHeader]];
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&len length:1];
+        [mData appendBytes:&tail length:1];
+        [self sendToDevice:mData];
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        self.closeRecord = finished;
+        uint8_t cmd = 0x04;
+        uint8_t dir = 0x01;
+        uint8_t len = 0x00;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&dir length:1];
+        [mData appendBytes:&len length:1];
+        [self sendToDevice:mData];
+    }
+
+}
+
+- (void)setDPI:(uint8_t)value completation:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if(self.connectType == TlwConnectType_hid){
+        self.setDpi  = finished;
+        uint8_t type = 0x06;
+        uint8_t len = 0x01;
+        uint8_t tail = 0xEF;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendData:[self getHidHeader]];
+        [mData appendBytes:&type length:1];
+        [mData appendBytes:&len length:1];
+        [mData appendBytes:&value length:1];
+        [mData appendBytes:&tail length:1];
+        [self sendToDevice:mData];
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        self.setDpi  = finished;
+        uint8_t cmd = 0x06;
+        uint8_t dir = 0x01;
+        uint8_t len = 0x01;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&dir length:1];
+        [mData appendBytes:&len length:1];
+        [mData appendBytes:&value length:1];
+        [self sendToDevice:mData];
+    }
+    
+
+}
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))finished{
+    if(self.connectType == TlwConnectType_hid){
+        self.getMouseInfo = finished;
+//        0x05fff1fec09f0114ef
+        uint8_t cmd = 0x0c;
+        uint8_t len = 0x00;
+        uint8_t tail = 0xEF;
+        
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendData:[self getHidHeader]];
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&len length:1];
+        [mData appendBytes:&tail length:1];
+        [self sendToDevice:mData];
+    }
+    else if (self.connectType == TlwConnectType_bluetooth){
+        self.getMouseInfo = finished;
+        uint8_t cmd = 0x0c;
+        uint8_t dir = 0x01;
+        uint8_t len = 0x00;
+        NSMutableData *mData = NSMutableData.data;
+        [mData appendBytes:&cmd length:1];
+        [mData appendBytes:&dir length:1];
+        [mData appendBytes:&len length:1];
+        [self sendToDevice:mData];
+    }
+}
+
+#pragma mark - ble
+- (void)bleReceiveAiData:(NSData *)data{
+    [self.connectManager resetTimeOut];
+    [self.audioManager receiveData:data];
+}
+
+-(void)bleReceiveDeviceData:(NSData *)data{
+    NSData *typeData = [data subdataWithRange:NSMakeRange(0, 1)];
+    uint8 type;
+    [typeData getBytes:&type length:1];
+    
+    NSData *stateData = [data subdataWithRange:NSMakeRange(1, 1)];
+    uint8 state;
+    [stateData getBytes:&state length:1];
+    BOOL success = state == 0x02;
+    
+    NSData *lenData = [data subdataWithRange:NSMakeRange(2, 1)];
+    uint8 len;
+    [lenData getBytes:&len length:1];
+    
+    switch (type) {
+        case 0x6A:
+            //语音开启回复
+            [self showLog:[NSString stringWithFormat:@"语音开启回复 %@",@(success)]];
+            self.openRecord(success, nil);
+            break;
+        case 0x6B:
+            //语音停止回复
+            [self showLog:[NSString stringWithFormat:@"语音停止回复 %@",@(success)]];
+            self.closeRecord(success, nil);
+            break;
+        case 0x96:
+            //设置DPI回复
+            [self showLog:[NSString stringWithFormat:@"设置DPI回复 %@",@(success)]];
+            self.setDpi(success, nil);
+            break;
+        case 0x9c:
+        { //鼠标状态查询回复
+            uint16_t version;
+            [[data subdataWithRange:NSMakeRange(3, 2)] getBytes:&version length:2];
+            
+            uint16_t DPI;
+            [[data subdataWithRange:NSMakeRange(5, 2)] getBytes:&DPI length:2];
+            
+            uint16_t batteryState;
+            [[data subdataWithRange:NSMakeRange(7, 1)] getBytes:&batteryState length:1];
+            
+            uint16_t batteryValue;
+            [[data subdataWithRange:NSMakeRange(8, 1)] getBytes:&batteryValue length:1];
+            
+            TlwDeviceInfo *info = [[TlwDeviceInfo alloc] init];
+            info.version = version;
+            info.DPI = DPI;
+            info.batteryState = batteryState;
+            info.batteryValue = batteryValue;
+            [self showLog:info.description];
+            self.getMouseInfo(info, nil);
+        }
+            break;
+        case 0x9f:
+        {
+            //鼠标按键上报
+            NSData *subData = [data subdataWithRange:NSMakeRange(3, 1)];
+            [self sendCmdData:subData];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+#pragma mark - hid
+- (void)hidReceiveDeviceData:(NSData *)data{
+//    0x05fff1fec09f0114ef
+    uint8 type;
+    [[data subdataWithRange:NSMakeRange(5, 1)] getBytes:&type length:1];
+    
+    NSData *lenData = [data subdataWithRange:NSMakeRange(6, 1)];
+    uint8 len;
+    [lenData getBytes:&len length:1];
+    
+    switch (type) {
+        case 0x6A:
+        {
+            //语音开启回复
+            uint8 state;
+            [[data subdataWithRange:NSMakeRange(6, 1)] getBytes:&state length:1];
+            BOOL success = state == 0x02;
+            [self showLog:[NSString stringWithFormat:@"语音开启回复 %@",@(success)]];
+            self.openRecord(success, nil);
+            [self startRecord];
+            break;
+        }
+
+        case 0x6B:
+        {
+            //语音停止回复
+            uint8 state;
+            [[data subdataWithRange:NSMakeRange(6, 1)] getBytes:&state length:1];
+            BOOL success = state == 0x02;
+
+            [self showLog:[NSString stringWithFormat:@"语音停止回复 %@",@(success)]];
+            [self stopRecord];
+            self.closeRecord(success, nil);
+            break;
+        }
+        case 0x96:
+        {
+            //设置DPI回复
+            uint8 state;
+            [[data subdataWithRange:NSMakeRange(6, 1)] getBytes:&state length:1];
+            BOOL success = state == 0x02;
+
+            [self showLog:[NSString stringWithFormat:@"设置DPI回复 %@",@(success)]];
+            self.setDpi(success, nil);
+            break;
+        }
+        case 0x9c:
+        { //鼠标状态查询回复
+            uint16_t version;
+            [[data subdataWithRange:NSMakeRange(3, 2)] getBytes:&version length:2];
+            
+            uint16_t DPI;
+            [[data subdataWithRange:NSMakeRange(5, 2)] getBytes:&DPI length:2];
+            
+            uint16_t batteryState;
+            [[data subdataWithRange:NSMakeRange(7, 1)] getBytes:&batteryState length:1];
+            
+            uint16_t batteryValue;
+            [[data subdataWithRange:NSMakeRange(8, 1)] getBytes:&batteryValue length:1];
+            
+            TlwDeviceInfo *info = [[TlwDeviceInfo alloc] init];
+            info.version = version;
+            info.DPI = DPI;
+            info.batteryState = batteryState;
+            info.batteryValue = batteryValue;
+            [self showLog:info.description];
+            self.getMouseInfo(info, nil);
+        }
+            break;
+        case 0x9f:
+        {
+            NSData *subData = [data subdataWithRange:NSMakeRange(7, 1)];
+            [self sendCmdData:subData];
+            
+            uint8 cmd;
+            [subData getBytes:&cmd length:1];
+
+            if(cmd == 0x13 || cmd == 0x11 || cmd == 0x01 ||cmd == 0x09){
+                self.recordManager.startCmd = subData;
+                [self startRecord];
+            }
+            else if(cmd == 0x14 || cmd == 0x12 || cmd == 0x02 ||cmd == 0x10){
+                [self stopRecord];
+            }
+        }
+            break;
+        default:
+            break;
+    }
+}
+- (NSData *)getHidHeader{
+    uint8_t head1 = 0x05;
+    uint16_t head2 = 0xFFF1;
+    uint8_t head3 = 0xfe;
+    uint8_t head4 = 0x00;
+
+    NSMutableData *mData = NSMutableData.data;
+    [mData appendBytes:&head1 length:1];
+    [mData appendBytes:&head2 length:2];
+    [mData appendBytes:&head3 length:1];
+    [mData appendBytes:&head4 length:1];
+    
+    return mData;
+}
+#pragma mark - other
+- (void)sendRecordData:(NSData *)data{
+    [self showLog:[NSString stringWithFormat:@"[send][audio] %@",data]];
+    if (self.delegate)  [self.delegate tlwMouseSendRecordData:data];
+}
+
+- (void)sendCmdData:(NSData *)data{
+    [self showLog:[NSString stringWithFormat:@"[send] %@",data]];
+    if (self.delegate) [self.delegate tlwMouseSendCmdData:data];
+}
+
+- (void)showLog:(NSString *)log{
+    if (self.delegate && [self.delegate respondsToSelector:@selector(aiMouseAddLog:)]) {
+        [self.delegate aiMouseAddLog:[NSString stringWithFormat:@"[AiMouseSdk]%@",log]];
+    }
+    if(self.logLevel == TlwMouseLogLevel_info){
+        NSLog(@"[AiMouseSdk]%@",log);
+    }
+}
+
+
+- (void)sendToDevice:(NSData *)data{
+    [self showLog:[NSString stringWithFormat:@"[send][info] %@",data]];
+    [self.delegate tlwMouseSendDeviceInfoData:data];
+}
+#pragma mark - deviceInfo
+- (void)startRecord{
+    [self.recordManager start];
+}
+
+- (void)stopRecord{
+    if (self.recordManager.recording ) {
+        [self.recordManager stop];
+    }
+}
+
+#pragma mark - TlwConnectManagerDelegate
+-(void)TlwConnectManager:(TlwConnectManager *)manager connectStateDidChange:(BOOL)connected{
+    _connected = connected;
+    if([self.delegate respondsToSelector:@selector(aiMouseConnectStateDidChange:)]) [self.delegate aiMouseConnectStateDidChange:connected];
+}
+-(void)TlwConnectManagerReSendData:(TlwConnectManager *)manager{
+//    __weak typeof(self) ws = self;
+//    [self.deviceInfoManager getVersion:^(NSString * _Nonnull version, NSError * _Nonnull error) {
+//        if (error.code == AiMouseErrorCode_timeOut) {
+//            [ws.connectManager addTimeOut];
+//        }
+//    }];
+}
+-(void)TlwConnectManager:(TlwConnectManager *)manager outputLog:(NSString *)log{
+    [self showLog:log];
+}
+@end
+
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.h
new file mode 100644 (file)
index 0000000..3d499c6
--- /dev/null
@@ -0,0 +1,25 @@
+//
+//  TlwRecordManager.h
+//  AudioRecorder
+//
+//  Created by incar on 2024/3/29.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwRecordManager : NSObject
+@property (nonatomic,assign)BOOL recording;
+@property (nonatomic,strong)NSData *startCmd;
+@property (nonatomic,readonly)NSData *stopCmd;
+@property (nonatomic,strong)void (^didRecordData)(NSData *data);
+
+- (void)checkPermissions;
+
+- (void)start;
+
+- (void)stop;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwRecordManager.m
new file mode 100644 (file)
index 0000000..7b3bf47
--- /dev/null
@@ -0,0 +1,194 @@
+//
+//  TlwRecordManager.m
+//  AudioRecorder
+//
+//  Created by incar on 2024/3/29.
+//
+
+#import "TlwRecordManager.h"
+#import <AVFoundation/AVFoundation.h>
+
+@interface TlwRecordManager()
+@property (nonatomic,assign)BOOL isAllowRecord;
+@property (nonatomic,strong)AVAudioEngine *audioEngine;
+@property (nonatomic,strong)AVAudioFile *outputFile;
+
+@end
+
+@implementation TlwRecordManager
+//+(TlwRecordManager *)shared{
+//    static TlwRecordManager *manager = nil;
+//    static dispatch_once_t oncePredicate;
+//    dispatch_once(&oncePredicate, ^{
+//        manager = [[TlwRecordManager alloc]init];
+//    });
+//    return manager;
+//}
+
+
+-(instancetype)init{
+    if (self = [super init ]) {
+        self.isAllowRecord = false;
+//        [self checkPermissions];
+    }
+    return self;
+}
+
+- (void)start{
+    if (self.isAllowRecord == false) {
+        return;
+    }
+    //    let audioSession = AVAudioSession.sharedInstance()
+    //    do {
+    //      try audioSession.setCategory(.playAndRecord, options: .defaultToSpeaker)
+    //      try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
+    //    catch let error as NSError {
+    //      print("ERROR:", error)
+    //    }
+    self.recording = true;
+    
+    self.audioEngine = [[AVAudioEngine alloc] init];
+    AVAudioInputNode *input = self.audioEngine.inputNode;
+    AVAudioNodeBus bus = 0;
+    AVAudioFormat *inputFormat = [input inputFormatForBus:bus];
+    //    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",NSTemporaryDirectory()];
+    //    NSURL *fileURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject URLByAppendingPathComponent:@"record.caf"];
+    //    [self showLog:fileURL.absoluteString];
+    //    NSError *err0;
+    //    self.outputFile = [[AVAudioFile alloc] initForWriting:fileURL settings:inputFormat.settings commonFormat:inputFormat.commonFormat interleaved:inputFormat.isInterleaved error:&err0];
+    //    if (err0) {
+    //        [self showLog:[NSString stringWithFormat:@"设置写入路径 %@", err0.description]];
+    //    }
+    //    else{
+    //        [self showLog:@"写入路径设置成功"];
+    //    }
+    //    self.recordData = [[NSMutableData alloc] init];
+    __weak typeof(self) ws = self;
+    [input installTapOnBus:bus bufferSize:32 format:inputFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
+        NSData *data = [ws pcm_downsample:buffer];
+        if (ws.didRecordData) {
+            ws.didRecordData(data);
+        }
+//        NSLog(@"%@",data);
+//        NSError *err0;
+        //        [ws.outputFile writeFromBuffer:buffer error:&err0];
+        //        if (err0) {
+        //            [self showLog:[NSString stringWithFormat:@"写入失败 %@", err0.description]];
+        //        }
+        //        else{
+        //            [self showLog:@"写入成功"];
+        //        }
+    }];
+    
+    NSError *err1;
+    [self.audioEngine prepare];
+    [self.audioEngine startAndReturnError:&err1];
+    if (err1) {
+        [self showLog:[NSString stringWithFormat:@"启动录音失败 %@", err1.description]];
+    }
+    else{
+        [self showLog:@"启动成功"];
+    }
+}
+
+- (void)stop{
+    [self.audioEngine stop];
+    self.recording = false;
+    //    [self finishRecored:self.recordData];
+}
+
+- (void)checkPermissions{
+    switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio]) {
+        case AVAuthorizationStatusAuthorized:
+            self.isAllowRecord = true;
+            [self showLog:@"录音已经授权"];
+            break;
+        case AVAuthorizationStatusNotDetermined:
+        {
+            [self showLog:@"录音未授权"];
+            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
+                if (granted) {
+                    self.isAllowRecord = true;
+                    [self showLog:@"录音已经授权"];
+                }
+                else{
+                    [self showLog:@"已拒绝"];
+                }
+            }];
+        }
+            break;
+        case AVAuthorizationStatusDenied:
+            [self showLog:@"已拒绝"];
+            [self openSettings];
+            break;
+        default:
+            [self showLog:@"已拒绝"];
+            [self openSettings];
+            break;
+    }
+}
+
+- (void)openSettings{
+//    NSURL *url = [NSURL URLWithString:@""];
+}
+
+- (void)showLog:(NSString *)log{
+//    NSLog(@"%@",log);
+}
+
+//将录音数据保存
+- (void)finishRecored:(NSData *)data{
+    NSString *doc = NSTemporaryDirectory();
+    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc];
+    NSError *err;
+    [data writeToFile:fullPath options:NSDataWritingAtomic error:&err];
+    if (err) {
+        [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]];
+    }
+    else{
+        [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]];
+    }
+}
+
+-(NSData*) pcm_downsample:(AVAudioPCMBuffer*)buffer {
+    //位深转换 float32 转 int16_t
+    NSData* pData = [[NSData alloc] initWithBytes:buffer.floatChannelData[0] length:buffer.frameLength * 4];
+    Byte*dataByte = (Byte*)[pData bytes];
+    NSUInteger srcSize = pData.length/4;
+    Float32 vf32[srcSize];
+    memcpy(vf32, &dataByte[0], pData.length);
+    int16_t bArr[srcSize];
+    int r;
+    for(size_t i =0; i < srcSize; ++i) {
+        float x = vf32[i];
+        float c;
+        c = ((x < -1) ? -1: ((x >1) ?1: x));
+        c = c +1;
+        r = (int)(c *32767.5f);
+        r = r -32768;
+        bArr[i] = (short)r;
+    }
+    //码率转换 44100 转 16000
+    uint32_t last_pos = (uint32_t)(srcSize -1);
+    uint32_t dstSize = (srcSize/44100.0)*16000;
+    int16_t bArr16[dstSize];
+    for(uint32_t idx =0; idx < dstSize; idx++) {
+        float index = ((float) idx *44100.0) / (16000.0);
+        uint32_t p1 = (uint32_t) index;
+        float coef = index - p1;
+        uint32_t p2 = (p1 == last_pos) ? last_pos : p1 +1;
+        bArr16[idx] = (int16_t) ((1.0f- coef) * bArr[p1] + coef * bArr[p2]);
+    }
+    NSMutableData *data1 = [[NSMutableData alloc] init];
+    for(NSUInteger i =0; i < dstSize; i++) {
+        unsigned char valChar[2];
+        valChar[0] =0xff& bArr16[i];
+        valChar[1] = (0xff00& bArr16[i]) >>8;
+        [data1 appendBytes:valChar length:2];
+    }
+    return data1;
+}
+
+
+
+@end
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.h
new file mode 100644 (file)
index 0000000..4eb7bb4
--- /dev/null
@@ -0,0 +1,44 @@
+//
+//  TlwTools.h
+//  AiMouse
+//
+//  Created by incar on 2024/2/28.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwTools : NSObject
++ (NSArray *)stringToArray:(NSString *)jsonString;
++ (NSString *)arrayToString:(NSArray *)arr;
+
++ (NSDictionary *)stringToDictionary:(NSString *)jsonString;
++ (NSString *)dictionaryToString:(NSDictionary *)dic;
+
++ (NSDictionary *)dataToDictionary:(NSData *)jsonData;
++ (NSData *)dictionaryToData:(NSDictionary *)dicJson;
+
+
++ (NSString *)dataToHexString:(NSData *)data;
+
++ (NSString *)dataToHexString_placeholder:(NSData *)data;
+
++ (NSData *)hexStringToData:(NSString *)hexStr ;
+
++ (NSString *)dataToUtf8String:(NSData *)data;
+
++ (NSString *)stringTomd5:(NSString *)str;
+
++ (NSString *)stringToHmacSha1:(NSString*)key text:(NSString*)text;
+
++ (NSString *)MD5ForUpper32:(NSString *)str;
+
++ (NSData *)dataWithHexString:(NSString *)hexStr;
+
++(NSString *)randomString:(NSInteger)length;
+
++(NSString *)getCurrentTime;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.m b/MouseSdk/AiMouseSdk/TlwMouseSdk/TlwTools.m
new file mode 100644 (file)
index 0000000..be8f60a
--- /dev/null
@@ -0,0 +1,226 @@
+//
+//  TlwTools.m
+//  AiMouse
+//
+//  Created by incar on 2024/2/28.
+//
+
+#import "TlwTools.h"
+#import <CommonCrypto/CommonCryptor.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CommonCrypto/CommonHMAC.h>
+
+@implementation TlwTools
++ (NSArray *)stringToArray:(NSString *)jsonString{
+    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+    NSError *error;
+    NSArray *arr = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
+    return arr;
+}
++ (NSString *)arrayToString:(NSArray *)arr{
+    NSError *error;
+    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:NSJSONWritingPrettyPrinted error:&error];
+    NSString * string = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+    return  string;
+}
++ (NSDictionary *)stringToDictionary:(NSString *)jsonString{
+    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+    if (jsonData == nil) {
+        return nil;
+    }
+    NSError *error;
+    NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
+    return dict;
+}
++ (NSString *)dictionaryToString:(NSDictionary *)dic{
+    if ([dic isKindOfClass:[NSDictionary class]]) {
+        NSError *error;
+        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&error];
+        NSString * string = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+        return  string;
+    }
+    else{
+        return @"";
+    }
+}
+
++ (NSDictionary *)dataToDictionary:(NSData *)jsonData{
+    if (![jsonData isKindOfClass:[NSData class]] || jsonData.length < 1) {
+        return nil;
+    }
+    id jsonObj = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
+    if (![jsonObj isKindOfClass:[NSDictionary class]]) {
+        return nil;
+    }
+    return [NSDictionary dictionaryWithDictionary:(NSDictionary *)jsonObj];
+}
+
+
++ (NSData *)dictionaryToData:(NSDictionary *)dicJson{
+    if (![dicJson isKindOfClass:[NSDictionary class]]) {
+        return nil;
+    }
+    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dicJson options:0 error:nil];
+    if (![jsonData isKindOfClass:[NSData class]]) {
+        return nil;
+    }
+    return jsonData;
+}
+
++ (NSString *)dataToHexString:(NSData *)data {
+    NSUInteger length = data.length;
+    NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
+    const unsigned char *byte = data.bytes;
+    for (int i = 0; i < length; i++, byte++) {
+        [result appendFormat:@"%02X", *byte];
+    }
+    return result;
+}
+
++ (NSString *)dataToHexString_placeholder:(NSData *)data {
+    NSUInteger length = data.length;
+    NSString *placeholder = @" ";
+    NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
+    const unsigned char *byte = data.bytes;
+    for (int i = 0; i < length; i++, byte++) {
+        [result appendFormat:@"%02X%@", *byte, placeholder];
+    }
+    return result;
+}
+
++ (NSData *)hexStringToData:(NSString *)hexStr {
+    hexStr = [hexStr stringByReplacingOccurrencesOfString:@" " withString:@""];
+    hexStr = [hexStr lowercaseString];
+    NSUInteger len = hexStr.length;
+    if (!len) return nil;
+    unichar *buf = malloc(sizeof(unichar) * len);
+    if (!buf) return nil;
+    [hexStr getCharacters:buf range:NSMakeRange(0, len)];
+    
+    NSMutableData *result = [NSMutableData data];
+    unsigned char bytes;
+    char str[3] = { '\0', '\0', '\0' };
+    int i;
+    for (i = 0; i < len / 2; i++) {
+        str[0] = buf[i * 2];
+        str[1] = buf[i * 2 + 1];
+        bytes = strtol(str, NULL, 16);
+        [result appendBytes:&bytes length:1];
+    }
+    free(buf);
+    return result;
+}
+
++ (NSString *)dataToUtf8String:(NSData *)data {
+    if (data.length > 0) {
+        return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+    }
+    return @"";
+}
+
+
++ (NSString *)stringTomd5:(NSString *)str {
+    if (str.length == 0) {
+        return nil;
+    }
+    // 初始化C字符数组
+    const char* original_str = (const char *)[[str dataUsingEncoding:NSUTF8StringEncoding] bytes];
+    // 盛放数字校验的字符数组(长度为16bytes)
+    unsigned char digist[CC_MD5_DIGEST_LENGTH]; //CC_MD5_DIGEST_LENGTH = 16
+    CC_MD5(original_str, (uint)strlen(original_str), digist);
+    //        NSMutableString* outPutStr = [NSMutableString stringWithCapacity:10];
+    NSMutableString* outPutStr = [NSMutableString new];
+    for(int  i =0; i<CC_MD5_DIGEST_LENGTH;i++){
+        [outPutStr appendFormat:@"%02X",digist[i]];//小写x表示输出的是小写MD5,大写X表示输出的是大写MD5
+    }
+    return outPutStr;
+}
+
+//HmacSHA1加密
++ (NSString *)stringToHmacSha1:(NSString*)key text:(NSString*)text{
+    const char *cKey  = [key cStringUsingEncoding:NSUTF8StringEncoding];
+    const char *cData = [text cStringUsingEncoding:NSUTF8StringEncoding];
+    uint8_t cHMAC[CC_SHA1_DIGEST_LENGTH];
+    CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
+    //NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:CC_SHA1_DIGEST_LENGTH];
+    NSString *hash;
+    NSMutableString * output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
+    for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
+        [output appendFormat:@"%02X", cHMAC[i]];
+    hash = output;
+    return hash;
+}
+
++ (NSString *)MD5ForUpper32:(NSString *)str {
+    const char* input = [str UTF8String];
+    unsigned char result[CC_MD5_DIGEST_LENGTH];
+    CC_MD5(input, (CC_LONG)strlen(input), result);
+    // 'CC_MD5' is deprecated: first deprecated in iOS 13.0 - This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).
+    NSMutableString *digest = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
+    for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
+        [digest appendFormat:@"%02X", result[i]];
+    }
+    
+    return digest;
+}
+
++ (NSData *)dataWithHexString:(NSString *)hexStr {
+    hexStr = [hexStr stringByReplacingOccurrencesOfString:@" " withString:@""];
+    hexStr = [hexStr lowercaseString];
+    NSUInteger len = hexStr.length;
+    if (!len) return nil;
+    unichar *buf = malloc(sizeof(unichar) * len);
+    if (!buf) return nil;
+    [hexStr getCharacters:buf range:NSMakeRange(0, len)];
+    
+    NSMutableData *result = [NSMutableData data];
+    unsigned char bytes;
+    char str[3] = { '\0', '\0', '\0' };
+    int i;
+    for (i = 0; i < len / 2; i++) {
+        str[0] = buf[i * 2];
+        str[1] = buf[i * 2 + 1];
+        bytes = strtol(str, NULL, 16);
+        [result appendBytes:&bytes length:1];
+    }
+    free(buf);
+    return result;
+}
+
++(NSString *)randomString:(NSInteger)length {
+    char letters[52] = "1234567891234567891234567891234567891234567891234567";
+//    char letters[10] = "012345678901234567890123456789012345678901234567890123456789";
+    char *output = malloc(sizeof(char) * (length + 1));
+    for (int i = 0; i < length; i++) {
+        int index = arc4random_uniform(52);
+        output[i] = letters[index];
+    }
+    output[length] = '\0';
+    
+    return [[NSString alloc] initWithBytesNoCopy:output length:length encoding:NSUTF8StringEncoding freeWhenDone:YES];
+}
+
++(NSString *)getCurrentTime{
+//    @"2024-08-05T10:07:39Z"
+    NSDate *date = [NSDate date];
+//    NSLog(@"当前零时区时间 %@", date);
+    
+    NSDateFormatter *dateFormaterA = [[NSDateFormatter alloc]init];
+    [dateFormaterA setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; // 设置时区为格林尼治标准时区
+    [dateFormaterA setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSString *dateStrA = [dateFormaterA stringFromDate:date];
+    NSString *text1 = [[dateStrA componentsSeparatedByString:@" "] componentsJoinedByString:@"T"];
+    NSString *text2 = [text1 stringByAppendingString:@"Z"];
+//    NSLog(@"%@",text2);
+    
+    
+    // 如果你需要将这个UTC时间显示为格林尼治时间,‌你可以这样做:‌
+//    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+//    [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; // 设置时区为格林尼治标准时区
+//    NSString *gmTimeString = [dateFormatter stringFromDate:date]; // 将UTC日期转换为字符串表示形式,‌时区设置为GMT
+//    NSLog(@"%@ == ",gmTimeString);
+    
+    return  text2;
+}
+@end
+
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.c b/MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.c
new file mode 100644 (file)
index 0000000..45fd010
--- /dev/null
@@ -0,0 +1,148 @@
+/********************************************************************************************************
+ * @file       adpcm.c
+ *
+ * @brief      This is the source file for BLE SDK
+ *
+ * @author     BLE GROUP
+ * @date       06,2020
+ *
+ * @par     Copyright (c) 2020, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
+ *          All rights reserved.
+ *
+ *          Redistribution and use in source and binary forms, with or without
+ *          modification, are permitted provided that the following conditions are met:
+ *
+ *              1. Redistributions of source code must retain the above copyright
+ *              notice, this list of conditions and the following disclaimer.
+ *
+ *              2. Unless for usage inside a TELINK integrated circuit, redistributions
+ *              in binary form must reproduce the above copyright notice, this list of
+ *              conditions and the following disclaimer in the documentation and/or other
+ *              materials provided with the distribution.
+ *
+ *              3. Neither the name of TELINK, nor the names of its contributors may be
+ *              used to endorse or promote products derived from this software without
+ *              specific prior written permission.
+ *
+ *              4. This software, with or without modification, must only be used with a
+ *              TELINK integrated circuit. All other usages are subject to written permission
+ *              from TELINK and different commercial license may apply.
+ *
+ *              5. Licensee shall be solely responsible for any claim to the extent arising out of or
+ *              relating to such deletion(s), modification(s) or alteration(s).
+ *
+ *          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ *          ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *          WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *          DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
+ *          DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *          (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *          LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *          ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *          (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *******************************************************************************************************/
+
+#include       "adpcm.h"
+
+static const signed char idxtbl[] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8};
+static const unsigned short steptbl[] = {
+ 7,  8,  9,  10,  11,  12,  13,  14,  16,  17,
+ 19,  21,  23,  25,  28,  31,  34,  37,  41,  45,
+ 50,  55,  60,  66,  73,  80,  88,  97,  107, 118,
+ 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+ 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767   };
+
+#define         NUM_OF_ORIG_SAMPLE                             2
+
+void adpcm_to_pcm (signed short *ps, signed short *pd, int len)
+{
+       int i;
+       //byte2,byte1: predict;  byte3: predict_idx; byte4:adpcm data len
+       int predict = ps[0];
+       int predict_idx = ps[1] & 0xff;
+//     int adpcm_len = (ps[1]>>8) & 0xff;
+       unsigned char *pcode = (unsigned char *) (ps + NUM_OF_ORIG_SAMPLE);
+       unsigned char code;
+       code = *pcode ++;
+
+       //byte5- byte128: 124 byte(62 sample) adpcm data
+       for (i=0; i<len; i++) {
+
+               if (1) {
+                       int step = steptbl[predict_idx];
+
+                       int diffq = step >> 3;
+
+                       if (code & 4) {
+                               diffq = diffq + step;
+                       }
+                       step = step >> 1;
+                       if (code & 2) {
+                               diffq = diffq + step;
+                       }
+                       step = step >> 1;
+                       if (code & 1) {
+                               diffq = diffq + step;
+                       }
+
+                       if (code & 8) {
+                               predict = predict - diffq;
+                       }
+                       else {
+                               predict = predict + diffq;
+                       }
+
+                       if (predict > 32767) {
+                               predict = 32767;
+                       }
+                       else if (predict < -32768) {
+                               predict = -32768;
+                       }
+
+                       predict_idx = predict_idx + idxtbl[code & 15];
+
+                       if(predict_idx < 0) {
+                               predict_idx = 0;
+                       }
+                       else if(predict_idx > 88) {
+                               predict_idx = 88;
+                       }
+
+                       if (i&1) {
+                               code = *pcode ++;
+                       }
+                       else {
+                               code = code >> 4;
+                       }
+               }
+
+               if (0 && i < NUM_OF_ORIG_SAMPLE) {
+                       *pd++ = ps[i];
+               }
+               else {
+                       *pd++ = predict;
+               }
+       }
+}
+
+//例子
+#if 0
+#define MIC_ADPCM_FRAME_SIZE           128                                             //一包 adpcm 的数据长度 128 byte
+#define MIC_SHORT_DEC_SIZE                     248
+#define DEC_BUFFER_SIZE                (MIC_SHORT_DEC_SIZE<<2)
+
+unsigned char          abuf_dec_wptr;                                                  // 0~3 , 4个缓冲 ,循环使用                               
+unsigned char          abuf_mic[MIC_ADPCM_FRAME_SIZE * 4];             //压缩的语音数据adpcm缓冲区 ,4个缓冲 ,每个缓冲区 128 byte , 循环使用
+signed short           abuf_dec[DEC_BUFFER_SIZE];                              //解压后存放的数据,注意数组类型是 16 bit,4个缓冲,循环使用
+
+//abuf_mic 源 ,  abuf_dec 目的 , 每次解压 128 byte adpcm 
+adpcm_to_pcm ((signed short *)(abuf_mic + (abuf_dec_wptr & 3) * MIC_ADPCM_FRAME_SIZE), abuf_dec + (abuf_dec_wptr & 3) * MIC_SHORT_DEC_SIZE, MIC_SHORT_DEC_SIZE );
+
+#endif
+
diff --git a/MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.h b/MouseSdk/AiMouseSdk/TlwMouseSdk/adpcm.h
new file mode 100644 (file)
index 0000000..0cb25a9
--- /dev/null
@@ -0,0 +1,51 @@
+/********************************************************************************************************
+ * @file       adpcm.h
+ *
+ * @brief      This is the header file for BLE SDK
+ *
+ * @author     BLE GROUP
+ * @date       06,2020
+ *
+ * @par     Copyright (c) 2020, Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
+ *          All rights reserved.
+ *
+ *          Redistribution and use in source and binary forms, with or without
+ *          modification, are permitted provided that the following conditions are met:
+ *
+ *              1. Redistributions of source code must retain the above copyright
+ *              notice, this list of conditions and the following disclaimer.
+ *
+ *              2. Unless for usage inside a TELINK integrated circuit, redistributions
+ *              in binary form must reproduce the above copyright notice, this list of
+ *              conditions and the following disclaimer in the documentation and/or other
+ *              materials provided with the distribution.
+ *
+ *              3. Neither the name of TELINK, nor the names of its contributors may be
+ *              used to endorse or promote products derived from this software without
+ *              specific prior written permission.
+ *
+ *              4. This software, with or without modification, must only be used with a
+ *              TELINK integrated circuit. All other usages are subject to written permission
+ *              from TELINK and different commercial license may apply.
+ *
+ *              5. Licensee shall be solely responsible for any claim to the extent arising out of or
+ *              relating to such deletion(s), modification(s) or alteration(s).
+ *
+ *          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ *          ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ *          WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *          DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
+ *          DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ *          (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ *          LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *          ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *          (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ *          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *******************************************************************************************************/
+#ifndef ADPCM_H_
+#define ADPCM_H_
+
+void adpcm_to_pcm(signed short *ps, signed short *pd, int len);
+
+#endif /* ADPCM_H_ */
index c02e0db..8eddb30 100644 (file)
                18BF1DF02B81AB8000B8D90F /* MouseSdkUITestsLaunchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 18BF1DEF2B81AB8000B8D90F /* MouseSdkUITestsLaunchTests.m */; };
                18BF1E222B83066200B8D90F /* BleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18BF1E212B83066200B8D90F /* BleManager.m */; };
                18BF1E762B832D2E00B8D90F /* ScanVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 18BF1E752B832D2E00B8D90F /* ScanVC.m */; };
+               18EF7B0A2C7605BF00AC5223 /* MouseSdkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B092C7605BF00AC5223 /* MouseSdkManager.m */; };
+               18EF7B0D2C76079E00AC5223 /* TlwVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B0C2C76079E00AC5223 /* TlwVC.m */; };
+               18EF7B102C7607C100AC5223 /* FrkVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B0F2C7607C100AC5223 /* FrkVC.m */; };
+               18EF7B172C760A5B00AC5223 /* TlwBleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B122C760A5A00AC5223 /* TlwBleManager.m */; };
+               18EF7B182C760A5B00AC5223 /* TlwHidManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B132C760A5A00AC5223 /* TlwHidManager.m */; };
+               18EF7B192C760A5B00AC5223 /* TlwDeviceManger.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B162C760A5A00AC5223 /* TlwDeviceManger.m */; };
+               18EF7B1D2C760FF700AC5223 /* TlwScanVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 18EF7B1B2C760FF700AC5223 /* TlwScanVC.m */; };
+               18EF7B1E2C760FF700AC5223 /* TlwScanVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 18EF7B1C2C760FF700AC5223 /* TlwScanVC.xib */; };
                18F3235D2B8ED78300CABA2E /* ScanCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F3235C2B8ED78300CABA2E /* ScanCell.m */; };
                18F328D52B98569D00CABA2E /* Config.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F328D42B98569D00CABA2E /* Config.m */; };
                8A6657BD1406314D4CBB7CEF /* libPods-MouseSdk.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0311DF3AD67ED792549B96BD /* libPods-MouseSdk.a */; };
                18BF1E752B832D2E00B8D90F /* ScanVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScanVC.m; sourceTree = "<group>"; };
                18C31D6F2BBAA6CC0089D656 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
                18CB080B2BA401D0005F5854 /* liblzma.5.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.5.tbd; path = usr/lib/liblzma.5.tbd; sourceTree = SDKROOT; };
+               18EF7B082C7605BF00AC5223 /* MouseSdkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MouseSdkManager.h; sourceTree = "<group>"; };
+               18EF7B092C7605BF00AC5223 /* MouseSdkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MouseSdkManager.m; sourceTree = "<group>"; };
+               18EF7B0B2C76079E00AC5223 /* TlwVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TlwVC.h; sourceTree = "<group>"; };
+               18EF7B0C2C76079E00AC5223 /* TlwVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TlwVC.m; sourceTree = "<group>"; };
+               18EF7B0E2C7607C100AC5223 /* FrkVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FrkVC.h; sourceTree = "<group>"; };
+               18EF7B0F2C7607C100AC5223 /* FrkVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FrkVC.m; sourceTree = "<group>"; };
+               18EF7B112C760A5A00AC5223 /* TlwHidManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwHidManager.h; sourceTree = "<group>"; };
+               18EF7B122C760A5A00AC5223 /* TlwBleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwBleManager.m; sourceTree = "<group>"; };
+               18EF7B132C760A5A00AC5223 /* TlwHidManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwHidManager.m; sourceTree = "<group>"; };
+               18EF7B142C760A5A00AC5223 /* TlwDeviceManger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwDeviceManger.h; sourceTree = "<group>"; };
+               18EF7B152C760A5A00AC5223 /* TlwBleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TlwBleManager.h; sourceTree = "<group>"; };
+               18EF7B162C760A5A00AC5223 /* TlwDeviceManger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TlwDeviceManger.m; sourceTree = "<group>"; };
+               18EF7B1A2C760FF700AC5223 /* TlwScanVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TlwScanVC.h; sourceTree = "<group>"; };
+               18EF7B1B2C760FF700AC5223 /* TlwScanVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TlwScanVC.m; sourceTree = "<group>"; };
+               18EF7B1C2C760FF700AC5223 /* TlwScanVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TlwScanVC.xib; sourceTree = "<group>"; };
                18F3235B2B8ED78300CABA2E /* ScanCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScanCell.h; sourceTree = "<group>"; };
                18F3235C2B8ED78300CABA2E /* ScanCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScanCell.m; sourceTree = "<group>"; };
                18F3249D2B9337D500CABA2E /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
                18BF1DCC2B81AB7C00B8D90F /* MouseSdk */ = {
                        isa = PBXGroup;
                        children = (
+                               18EF7B072C7604BF00AC5223 /* Ble */,
+                               18EF7B062C7604AA00AC5223 /* Tlw */,
                                18C31D6F2BBAA6CC0089D656 /* Info.plist */,
-                               18BF1E202B83066200B8D90F /* BleManager.h */,
-                               18BF1E212B83066200B8D90F /* BleManager.m */,
-                               1867E5A72BB2706F008FC5FB /* HidManager.h */,
-                               1867E5A82BB2706F008FC5FB /* HidManager.m */,
-                               182B548B2BB7EC9B009F53B6 /* DeviceManger.h */,
-                               182B548C2BB7EC9B009F53B6 /* DeviceManger.m */,
                                18BF1DCD2B81AB7C00B8D90F /* AppDelegate.h */,
                                18BF1DCE2B81AB7C00B8D90F /* AppDelegate.m */,
+                               18EF7B082C7605BF00AC5223 /* MouseSdkManager.h */,
+                               18EF7B092C7605BF00AC5223 /* MouseSdkManager.m */,
                                18BF1DD02B81AB7C00B8D90F /* ViewController.h */,
                                18BF1DD12B81AB7C00B8D90F /* ViewController.m */,
+                               18EF7B0B2C76079E00AC5223 /* TlwVC.h */,
+                               18EF7B0C2C76079E00AC5223 /* TlwVC.m */,
+                               18EF7B0E2C7607C100AC5223 /* FrkVC.h */,
+                               18EF7B0F2C7607C100AC5223 /* FrkVC.m */,
+                               18EF7B1A2C760FF700AC5223 /* TlwScanVC.h */,
+                               18EF7B1B2C760FF700AC5223 /* TlwScanVC.m */,
+                               18EF7B1C2C760FF700AC5223 /* TlwScanVC.xib */,
                                18BF1E742B832D2E00B8D90F /* ScanVC.h */,
                                18BF1E752B832D2E00B8D90F /* ScanVC.m */,
                                18F3235B2B8ED78300CABA2E /* ScanCell.h */,
                        path = MouseSdkUITests;
                        sourceTree = "<group>";
                };
+               18EF7B062C7604AA00AC5223 /* Tlw */ = {
+                       isa = PBXGroup;
+                       children = (
+                               18EF7B142C760A5A00AC5223 /* TlwDeviceManger.h */,
+                               18EF7B162C760A5A00AC5223 /* TlwDeviceManger.m */,
+                               18EF7B152C760A5A00AC5223 /* TlwBleManager.h */,
+                               18EF7B122C760A5A00AC5223 /* TlwBleManager.m */,
+                               18EF7B112C760A5A00AC5223 /* TlwHidManager.h */,
+                               18EF7B132C760A5A00AC5223 /* TlwHidManager.m */,
+                       );
+                       path = Tlw;
+                       sourceTree = "<group>";
+               };
+               18EF7B072C7604BF00AC5223 /* Ble */ = {
+                       isa = PBXGroup;
+                       children = (
+                               182B548B2BB7EC9B009F53B6 /* DeviceManger.h */,
+                               182B548C2BB7EC9B009F53B6 /* DeviceManger.m */,
+                               18BF1E202B83066200B8D90F /* BleManager.h */,
+                               18BF1E212B83066200B8D90F /* BleManager.m */,
+                               1867E5A72BB2706F008FC5FB /* HidManager.h */,
+                               1867E5A82BB2706F008FC5FB /* HidManager.m */,
+                       );
+                       path = Ble;
+                       sourceTree = "<group>";
+               };
                543EC4E5F9FC310D6B850947 /* Frameworks */ = {
                        isa = PBXGroup;
                        children = (
                        files = (
                                18BF1DD42B81AB7F00B8D90F /* Assets.xcassets in Resources */,
                                18BF1DD72B81AB7F00B8D90F /* Main.storyboard in Resources */,
+                               18EF7B1E2C760FF700AC5223 /* TlwScanVC.xib in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               18EF7B192C760A5B00AC5223 /* TlwDeviceManger.m in Sources */,
                                18F3235D2B8ED78300CABA2E /* ScanCell.m in Sources */,
                                18BF1DD22B81AB7C00B8D90F /* ViewController.m in Sources */,
                                18BF1DD92B81AB7F00B8D90F /* main.m in Sources */,
+                               18EF7B172C760A5B00AC5223 /* TlwBleManager.m in Sources */,
                                18F328D52B98569D00CABA2E /* Config.m in Sources */,
+                               18EF7B182C760A5B00AC5223 /* TlwHidManager.m in Sources */,
+                               18EF7B0D2C76079E00AC5223 /* TlwVC.m in Sources */,
                                182B548D2BB7EC9B009F53B6 /* DeviceManger.m in Sources */,
+                               18EF7B102C7607C100AC5223 /* FrkVC.m in Sources */,
                                18BF1E762B832D2E00B8D90F /* ScanVC.m in Sources */,
+                               18EF7B1D2C760FF700AC5223 /* TlwScanVC.m in Sources */,
+                               18EF7B0A2C7605BF00AC5223 /* MouseSdkManager.m in Sources */,
                                18BF1E222B83066200B8D90F /* BleManager.m in Sources */,
                                18BF1DCF2B81AB7C00B8D90F /* AppDelegate.m in Sources */,
                                1867E5A92BB2706F008FC5FB /* HidManager.m in Sources */,
                                SWIFT_OBJC_BRIDGING_HEADER = "MouseSdk/MouseSdk-Bridging-Header.h";
                                SWIFT_OPTIMIZATION_LEVEL = "-Onone";
                                SWIFT_VERSION = 5.0;
-                               USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/AiMouseSdk/AiMouseSdk\"";
+                               USER_HEADER_SEARCH_PATHS = (
+                                       "\"$(SRCROOT)/AiMouseSdk/AiMouseSdk\"",
+                                       "\"$(SRCROOT)/AiMouseSdk/TlwMouseSdk\"",
+                               );
                        };
                        name = Debug;
                };
                                SWIFT_EMIT_LOC_STRINGS = YES;
                                SWIFT_OBJC_BRIDGING_HEADER = "MouseSdk/MouseSdk-Bridging-Header.h";
                                SWIFT_VERSION = 5.0;
-                               USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/AiMouseSdk/AiMouseSdk\"";
+                               USER_HEADER_SEARCH_PATHS = (
+                                       "\"$(SRCROOT)/AiMouseSdk/AiMouseSdk\"",
+                                       "\"$(SRCROOT)/AiMouseSdk/TlwMouseSdk\"",
+                               );
                        };
                        name = Release;
                };
index a1373c5..4a419ff 100644 (file)
Binary files a/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate and b/MouseSdk/MouseSdk.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate differ
index b9bdb28..675cff0 100644 (file)
             </Locations>
          </BreakpointContent>
       </BreakpointProxy>
+      <BreakpointProxy
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
+         <BreakpointContent
+            uuid = "BE1E231B-12D2-47AA-A5E0-AAAA81898EEC"
+            shouldBeEnabled = "No"
+            ignoreCount = "0"
+            continueAfterRunningActions = "No"
+            filePath = "MouseSdk/TlwVC.m"
+            startingColumnNumber = "9223372036854775807"
+            endingColumnNumber = "9223372036854775807"
+            startingLineNumber = "23"
+            endingLineNumber = "23"
+            landmarkName = "TlwVC"
+            landmarkType = "3">
+         </BreakpointContent>
+      </BreakpointProxy>
    </Breakpoints>
 </Bucket>
similarity index 84%
rename from MouseSdk/MouseSdk/BleManager.h
rename to MouseSdk/MouseSdk/Ble/BleManager.h
index 536301f..b71c66a 100644 (file)
@@ -9,8 +9,8 @@
 #import <CoreBluetooth/CoreBluetooth.h>
 #import "AiMouseSdk.h"
 
-#define kDidConnectBleNotification @"kDidConnectBleNotification"
-#define kDidDisconnectBleNotification @"kDidDisconnectBleNotification"
+#define kTlwDidConnectBleNotification @"kTlwDidConnectBleNotification"
+#define kTlwDidDisconnectBleNotification @"kTlwDidDisconnectBleNotification"
 
 
 
@@ -19,8 +19,6 @@
 @property (nonatomic,strong)void (^didConnectDevice)(CBPeripheral *peripheral);/// 连接成功
 @property (nonatomic,strong)void (^didDisConnectDevice)(CBPeripheral *peripheral);/// 断开连接
 @property (nonatomic,strong)void (^authCallBack)(BOOL isSuccess);/// 鉴权回调
-/// 单利
-+(BleManager *)shared;
 
 /// 扫描
 - (void)startScanDevice;
similarity index 96%
rename from MouseSdk/MouseSdk/BleManager.m
rename to MouseSdk/MouseSdk/Ble/BleManager.m
index 57ee6ed..303523a 100644 (file)
 @end
 
 @implementation BleManager
-+(BleManager *)shared{
-    static BleManager *manager = nil;
-    static dispatch_once_t oncePredicate;
-    dispatch_once(&oncePredicate, ^{
-        manager = [[BleManager alloc]init];
-    });
-    return manager;
-}
-
 - (instancetype)init{
     self = [super init];
     if (self) {
     peripheral.delegate = self;
     [peripheral discoverServices:nil];
     
-    [[NSNotificationCenter defaultCenter] postNotificationName:kDidConnectBleNotification object:nil];
+    [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidConnectBleNotification object:nil];
 }
 -(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
     [self showLog:[NSString stringWithFormat:@"断开连接。。。%@",error.description]];
-    [[NSNotificationCenter defaultCenter] postNotificationName:kDidDisconnectBleNotification object:nil];
+    [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidDisconnectBleNotification object:nil];
     if(self.didDisConnectDevice){
         self.didDisConnectDevice(peripheral);
     }
 // 连接状态改变
 - (void)aiMouseConnectStateDidChange:(BOOL)connected{
     if (connected) {
-        [[NSNotificationCenter defaultCenter] postNotificationName:kDidConnectBleNotification object:nil];
+        [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidConnectBleNotification object:nil];
     }
     else{
-        [[NSNotificationCenter defaultCenter] postNotificationName:kDidDisconnectBleNotification object:nil];
+        [[NSNotificationCenter defaultCenter] postNotificationName:kTlwDidDisconnectBleNotification object:nil];
     }
 }
 #pragma mark - public
similarity index 73%
rename from MouseSdk/MouseSdk/DeviceManger.h
rename to MouseSdk/MouseSdk/Ble/DeviceManger.h
index 7918f92..108b310 100644 (file)
@@ -7,16 +7,26 @@
 
 #import <Foundation/Foundation.h>
 #import "AiMouseSdk.h"
+#import "HidManager.h"
+#import "BleManager.h"
 
-NS_ASSUME_NONNULL_BEGIN
 
 @interface DeviceManger : NSObject
+@property (nonatomic,strong) BleManager *ble;
+@property (nonatomic,strong) HidManager *hid;
+
 @property (nonatomic, readonly) AiMouseConnectType connectType;//连接类型
 @property (nonatomic,strong)void (^authCallBack)(BOOL isSuccess);/// 鉴权回调
 @property (nonatomic,strong)void (^connectStateCallBack)(AiMouseConnectType connectType,BOOL isConnect);///是否连接成功
 
 +(DeviceManger *)shared;
 
+//开始,使用该sdk需要调用该方法
+- (void)start;
+
+//停止,停止使用sdk需要调用该方法
+- (void)stop;
+
 /// 切换连接类型
 /// - Parameter type: 连接类型
 - (void)switchConnect:(AiMouseConnectType)type;
@@ -26,4 +36,3 @@ NS_ASSUME_NONNULL_BEGIN
 
 @end
 
-NS_ASSUME_NONNULL_END
similarity index 84%
rename from MouseSdk/MouseSdk/DeviceManger.m
rename to MouseSdk/MouseSdk/Ble/DeviceManger.m
index 77f411b..09d8f74 100644 (file)
@@ -6,13 +6,10 @@
 //
 
 #import "DeviceManger.h"
-#import "HidManager.h"
-#import "BleManager.h"
+
 
 @interface DeviceManger()
 
-@property (nonatomic,strong) BleManager *ble;
-@property (nonatomic,strong) HidManager *hid;
 @end
 
 @implementation DeviceManger
     return manager;
 }
 
+//开始,使用该sdk需要调用该方法
+- (void)start{
+    [self setupBle];
+    [self setupHid];
+}
+
+//停止,停止使用sdk需要调用该方法
+- (void)stop{
+    self.ble = nil;
+    self.hid = nil;
+}
+
 - (instancetype)init{
     self = [super init];
     if (self) {
         [[AiMouse shared] setLogLevel:AiMouseLogLevel_info];
-        [self setupBle];
-        [self setupHid];
     }
     return self;
 }
@@ -51,7 +58,7 @@
 }
 
 - (void)setupHid{
-    self.hid = [HidManager shared];
+    self.hid = [[HidManager alloc] init];
     __weak typeof(self) ws = self;
     self.hid.authCallBack = ^(BOOL isSuccess) {
        if(ws.authCallBack)ws.authCallBack(isSuccess);
@@ -62,7 +69,7 @@
 }
 
 - (void)setupBle{
-    self.ble = [BleManager shared];
+    self.ble = [[BleManager alloc] init];
     __weak typeof(self) ws = self;
     self.ble.authCallBack = ^(BOOL isSuccess) {
        if(ws.authCallBack)ws.authCallBack(isSuccess);
@@ -88,8 +95,8 @@
 
 #pragma mark - notication
 - (void)setupBleNotification{
-    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidConnect:) name:kDidConnectBleNotification object:nil];
-    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidDisconnect:) name:kDidDisconnectBleNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidConnect:) name:kTlwDidConnectBleNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidDisconnect:) name:kTlwDidDisconnectBleNotification object:nil];
 }
 
 - (void)bleDidConnect:(NSNotification *)notification{
similarity index 95%
rename from MouseSdk/MouseSdk/HidManager.h
rename to MouseSdk/MouseSdk/Ble/HidManager.h
index 427f198..94b62f5 100644 (file)
@@ -14,8 +14,6 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic,strong)void (^connectStateCallBack)(BOOL isConnnect);/// 设备是否连上
 @property (nonatomic,strong)void (^authCallBack)(BOOL isSuccess);/// 鉴权回调
 
-+(HidManager *)shared;
-
 /// 获取设备信息
 - (void)getDeviceInfo:(void (^)(AiMouseDeviceInfo *deviceInfo,NSError *error))completation;
 
similarity index 87%
rename from MouseSdk/MouseSdk/HidManager.m
rename to MouseSdk/MouseSdk/Ble/HidManager.m
index 17e3f98..8e78d35 100644 (file)
@@ -9,6 +9,7 @@
 #include <IOKit/hid/IOHIDLib.h>
 #include <IOKit/hid/IOHIDKeys.h>
 #import "Config.h"
+#import "DeviceManger.h"
 
 //供应商id
 const long vendorID = 0x6675;
@@ -24,14 +25,6 @@ const long productID = 0x8601;
 @end
 
 @implementation HidManager
-+(HidManager *)shared{
-    static HidManager *manager = nil;
-    static dispatch_once_t oncePredicate;
-    dispatch_once(&oncePredicate, ^{
-        manager = [[HidManager alloc]init];
-    });
-    return manager;
-}
 
 - (instancetype)init{
     self = [super init];
@@ -51,9 +44,9 @@ const long productID = 0x8601;
     IOHIDManagerScheduleWithRunLoop(self.hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
     IOReturn ret = IOHIDManagerOpen(self.hidManager, 0L);
     if (ret != kIOReturnSuccess) {
-        NSLog(@"打开设备失败!");
+        NSLog(@"打开hid失败!");
     }else{
-        NSLog(@"打开设备成功!");
+        NSLog(@"打开hid成功!");
     }
     //设置匹配设备
     NSMutableDictionary* dict= [NSMutableDictionary dictionary];
@@ -78,13 +71,13 @@ const long productID = 0x8601;
 #pragma mark - Hid CallBack
 /// 匹配到设备回调
 static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
-    [[HidManager shared] showLog:[NSString stringWithFormat:@"发现设备 %@\n ",inIOHIDDeviceRef]];
+    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"发现设备 %@\n ",inIOHIDDeviceRef]];
     uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey));
     uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey));
     if(PrimaryUsagePage == 1 && PrimaryUsage == 0){
         //设备信息
-        [HidManager shared].deviceInfoCha = inIOHIDDeviceRef;
-        [[HidManager shared] connectDevice];
+        [DeviceManger shared].hid.deviceInfoCha = inIOHIDDeviceRef;
+        [[DeviceManger shared].hid connectDevice];
         //注册消息回调
         unsigned char InputBuffer[128];
         IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL);
@@ -99,7 +92,7 @@ static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOH
     }
     else if(PrimaryUsagePage == 65283 && PrimaryUsage == 0){
         //ai
-        [HidManager shared].aiCha = inIOHIDDeviceRef;
+        [DeviceManger shared].hid.aiCha = inIOHIDDeviceRef;
         //注册消息回调
         unsigned char InputBuffer[128];
         IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL);
@@ -116,23 +109,23 @@ static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOH
 
 /// 设备移除回调
 static void hidDisAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
-    [[HidManager shared] showLog:[NSString stringWithFormat:@"拔出设备 %@ ",inIOHIDDeviceRef]];
+    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"拔出设备 %@ ",inIOHIDDeviceRef]];
     uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey));
     uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey));
 
     if(PrimaryUsagePage == 1 && PrimaryUsage == 0){
-        [HidManager shared].deviceInfoCha = nil;
-        [HidManager shared].aiCha = nil;
+        [DeviceManger shared].hid.deviceInfoCha = nil;
+        [DeviceManger shared].hid.aiCha = nil;
         [[AiMouse shared] didDisconnect];
     }
 }
 
 static void hidReceiveData(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength){
-//    [[HidManager shared] showLog:[NSString stringWithFormat:@"[receive] 处理前 %@",[AiMouseTools dataToHexString:[[NSData alloc] initWithBytes:report length:reportLength]]]];
+//    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"[receive] 处理前 %@",[AiMouseTools dataToHexString:[[NSData alloc] initWithBytes:report length:reportLength]]]];
     
     NSInteger len = get_data_len(report, reportLength);
     NSData *data = [[NSData alloc] initWithBytes:report length:len];
-//    [[HidManager shared] showLog:[NSString stringWithFormat:@"[receive] 处理后 %@",[AiMouseTools dataToHexString:data]]];
+//    [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"[receive] 处理后 %@",[AiMouseTools dataToHexString:data]]];
     
     uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsagePageKey));
     uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(sender, CFSTR(kIOHIDPrimaryUsageKey));
@@ -197,7 +190,7 @@ static void hidReceiveData(void* context, IOReturn result, void* sender, IOHIDRe
 
 // 连接状态改变
 - (void)aiMouseConnectStateDidChange:(BOOL)connected{
-    [HidManager shared].connectStateCallBack(connected);
+    [DeviceManger shared].hid.connectStateCallBack(connected);
 }
 #pragma mark - public
 -(void)connectDevice{
@@ -258,7 +251,7 @@ static uint32_t IOHIDDevGetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStr
         if (tCFTypeRef != NULL && (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef))  ) {
             Boolean result = CFNumberGetValue((CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, &outValue);
             if (!result) {
-                [[HidManager shared] showLog:[NSString stringWithFormat:@"get %@ failed.", inKey]];
+                [[DeviceManger shared].hid showLog:[NSString stringWithFormat:@"get %@ failed.", inKey]];
             }
         }
     }
diff --git a/MouseSdk/MouseSdk/FrkVC.h b/MouseSdk/MouseSdk/FrkVC.h
new file mode 100644 (file)
index 0000000..26d0149
--- /dev/null
@@ -0,0 +1,16 @@
+//
+//  FrkVC.h
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FrkVC : NSViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/FrkVC.m b/MouseSdk/MouseSdk/FrkVC.m
new file mode 100644 (file)
index 0000000..449d613
--- /dev/null
@@ -0,0 +1,168 @@
+//
+//  FrkVC.m
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import "FrkVC.h"
+#import "ScanVC.h"
+#import "DeviceManger.h"
+
+@interface FrkVC ()
+@property(nonatomic,strong)NSTextField *typeLabel;///当前模式状态
+@property(nonatomic,strong)NSTextField *connectLabel;///连接状态
+@property(nonatomic,strong)NSTextField *authLabel;///鉴权状态
+@property(nonatomic,strong)NSTextField *deviceInfoLabel;///设备信息
+@end
+
+@implementation FrkVC
+
+-(instancetype)init{
+    if (self = [super init]) {
+//        [[DeviceManger shared] start];
+    }
+    return self;
+}
+
+-(void)dealloc{
+    [[DeviceManger shared] stop];
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    // Do any additional setup after loading the view.
+    [[DeviceManger shared] start];
+    [self setupUI];
+    [self setupDeviceManger];
+}
+
+- (void)loadView{
+    self.view = [[NSView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
+}
+
+- (void)setupUI{
+    CALayer *viewLayer = [CALayer layer];
+    NSView *backgroundView= [[NSView alloc]init];
+    [backgroundView setWantsLayer:YES];
+    [backgroundView setLayer:viewLayer];
+    backgroundView.layer.backgroundColor = [NSColor greenColor].CGColor;
+    [backgroundView setNeedsDisplay:YES];
+    [self.view addSubview:backgroundView];
+    
+    self.title = @"AI SDK";
+    {
+        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 80, 100, 45)];
+        btn.bordered = true;
+        btn.title = @"蓝牙扫描";
+        [btn setTarget:self];
+        [btn setAction:@selector(scanAction)];
+        btn.state = 1;
+        [self.view addSubview:btn];
+    }
+    {
+        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 180, 100, 45)];
+        btn.bordered = true;
+        btn.title = @"获取设备信息";
+        [btn setTarget:self];
+        [btn setAction:@selector(geviceInfoAction)];
+        btn.state = 1;
+        [self.view addSubview:btn];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 80, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"连接模式:--";
+        label.backgroundColor = NSColor.clearColor;
+        self.typeLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 120, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"连接状态:未连接";
+        label.backgroundColor = NSColor.clearColor;
+        self.connectLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 160, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"鉴权状态:unknow";
+        label.backgroundColor = NSColor.clearColor;
+        self.authLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 260, 300, 100)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"";
+        label.backgroundColor = NSColor.clearColor;
+        self.deviceInfoLabel = label;
+        [self.view addSubview:label];
+    }
+}
+
+#pragma mark - action
+- (void)scanAction{
+    ScanVC *vc = [[ScanVC alloc] initWithNibName:nil bundle:nil];
+    [self presentViewControllerAsModalWindow:vc];
+}
+
+/// 获取设备信息
+- (void)geviceInfoAction{
+    [[DeviceManger shared] getDeviceInfo:^(AiMouseDeviceInfo *deviceInfo, NSError *error) {
+        if (deviceInfo) {
+            NSString *info = deviceInfo.description;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                self.deviceInfoLabel.stringValue = info;
+            });
+        }
+        else{
+            NSString *info = [NSString stringWithFormat:@"error == %@",error.description];
+            NSLog(@"%@",info);
+            dispatch_async(dispatch_get_main_queue(), ^{
+                self.deviceInfoLabel.stringValue = info;
+            });
+        }
+    }];
+}
+- (void)showAuthResult:(BOOL)isSuccess{
+    dispatch_async(dispatch_get_main_queue(), ^{
+        self.authLabel.stringValue = isSuccess?@"鉴权状态:鉴权成功":@"鉴权状态:鉴权失败";
+        if(isSuccess){
+            [self geviceInfoAction];
+        }
+    });
+}
+- (void)connectStateDidChange:(AiMouseConnectType)type connectState:(BOOL)isConnected{
+    dispatch_async(dispatch_get_main_queue(), ^{
+        if(isConnected){
+            self.typeLabel.stringValue = type == AiMouseConnectType_bluetooth ?@"连接模式:蓝牙":@"连接模式:hid";
+            self.connectLabel.stringValue = isConnected?@"连接状态:已连接":@"连接状态:未连接";
+            [self geviceInfoAction];
+        }
+        else{
+            self.typeLabel.stringValue = @"连接模式:--";
+            self.connectLabel.stringValue = @"连接状态:未连接";
+        }
+    });
+}
+
+- (void)setupDeviceManger{
+    DeviceManger *manager = [DeviceManger shared];
+    __weak typeof(self) ws = self;
+    manager.authCallBack = ^(BOOL isSuccess) {
+        [ws showAuthResult:isSuccess];
+    };
+    manager.connectStateCallBack = ^(AiMouseConnectType type,BOOL isConnected) {
+        [ws connectStateDidChange:type connectState:isConnected];
+    };
+}
+
+@end
diff --git a/MouseSdk/MouseSdk/MouseSdkManager.h b/MouseSdk/MouseSdk/MouseSdkManager.h
new file mode 100644 (file)
index 0000000..e984e2e
--- /dev/null
@@ -0,0 +1,24 @@
+//
+//  MouseSdkManager.h
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NS_ENUM(NSInteger,MouseSdkType) {
+    TlwMouseLogLevel_frk = 1,//富瑞坤
+    TlwMouseLogLevel_tlw = 2,//泰凌微
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+
+@interface MouseSdkManager : NSObject
+@property (nonatomic,assign)MouseSdkType type;//默认是富瑞坤
+
++(MouseSdkManager *)shared;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/MouseSdkManager.m b/MouseSdk/MouseSdk/MouseSdkManager.m
new file mode 100644 (file)
index 0000000..ec8fda1
--- /dev/null
@@ -0,0 +1,27 @@
+//
+//  MouseSdkManager.m
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import "MouseSdkManager.h"
+
+@implementation MouseSdkManager
++(MouseSdkManager *)shared{
+    static MouseSdkManager *manager = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        manager = [[MouseSdkManager alloc]init];
+    });
+    return manager;
+}
+
+- (instancetype)init{
+    self = [super init];
+    if (self) {
+        self.type = TlwMouseLogLevel_frk;
+    }
+    return self;
+}
+@end
index 75ad42d..d63b398 100644 (file)
@@ -7,7 +7,7 @@
 
 #import "ScanVC.h"
 #import "ScanCell.h"
-#import "BleManager.h"
+#import "DeviceManger.h"
 @interface ScanVC ()<NSTableViewDelegate,NSTableViewDataSource>
 @property (nonatomic,strong) NSTableView *tableView;
 @property (nonatomic,strong) NSMutableArray<CBPeripheral *> *scanArray;
@@ -67,7 +67,7 @@
 }
 
 - (void)setupBleManger{
-    BleManager *manager = [BleManager shared];
+    BleManager *manager = [DeviceManger shared].ble;
     __weak typeof(self) weakSelf= self;
     __weak typeof(BleManager) *weakManager = manager;
     manager.discoverDevice = ^(CBPeripheral *peripheral){
 }
 
 - (void)tapDevice:(CBPeripheral *)peripheral{
-    [[BleManager shared] connectPeripheral:peripheral];
+    [[DeviceManger shared].ble connectPeripheral:peripheral];
 }
 
 - (void)connectSuccess{
-    [[BleManager shared] stopScanDevice];
+    [[DeviceManger shared].ble stopScanDevice];
     dispatch_async(dispatch_get_main_queue(), ^{
          NSAlert *alert = [NSAlert new];
          [alert addButtonWithTitle:@"确定"];
 }
 
 -(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
-//    NSString *columnID = tableColumn.identifier;
-//    NSString *strIdt = @"NSTableCellView";
-//    NSTableCellView *cell = [tableView makeViewWithIdentifier:strIdt owner:self];
-//    if (!cell) {
-//        cell = [[NSTableCellView alloc]init];
-//        cell.identifier = strIdt;
-//    }
-//    cell.wantsLayer = YES;
-//    cell.textField.stringValue = [NSString stringWithFormat:@"cell %ld",(long)row];
-//    
-//    return cell;
     ScanCell *cell = [[ScanCell alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, 44)];
     cell.peripheral = self.scanArray[row];
     __weak typeof(self) weakSelf = self;
diff --git a/MouseSdk/MouseSdk/Tlw/TlwBleManager.h b/MouseSdk/MouseSdk/Tlw/TlwBleManager.h
new file mode 100644 (file)
index 0000000..f93737a
--- /dev/null
@@ -0,0 +1,46 @@
+//
+//  TlwBleManager.h
+//  AliIotSdkDemo
+//
+//  Created by incar on 2024/1/30.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreBluetooth/CoreBluetooth.h>
+#import "TlwMouseSdk.h"
+
+#define kDidConnectBleNotification @"kDidConnectBleNotification"
+#define kDidDisconnectBleNotification @"kDidDisconnectBleNotification"
+
+
+
+@interface TlwBleManager : NSObject
+@property (nonatomic,strong)void (^discoverDevice)(CBPeripheral *peripheral) ;/// 扫描到设备
+@property (nonatomic,strong)void (^didConnectDevice)(CBPeripheral *peripheral);/// 连接成功
+@property (nonatomic,strong)void (^didDisConnectDevice)(CBPeripheral *peripheral);/// 断开连接
+/// 单利
++(TlwBleManager *)shared;
+
+/// 扫描
+- (void)startScanDevice;
+
+/// 停止扫描
+- (void)stopScanDevice;
+
+/// 连接设备
+/// - Parameter peripheral: 蓝牙设备
+- (void)connectPeripheral:(CBPeripheral *)peripheral;
+
+/// 获取设备信息
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))finished;
+
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+@end
+
diff --git a/MouseSdk/MouseSdk/Tlw/TlwBleManager.m b/MouseSdk/MouseSdk/Tlw/TlwBleManager.m
new file mode 100644 (file)
index 0000000..a815010
--- /dev/null
@@ -0,0 +1,267 @@
+//
+//  TlwBleManager.m
+//  AliIotSdkDemo
+//
+//  Created by incar on 2024/1/30.
+//
+
+#import "TlwBleManager.h"
+#import "TlwMouseSdk.h"
+#import "Config.h"
+
+// 服务id
+#define kServiceUUID @"FFF0"
+//ai特征
+#define kAiUUID @"00010203-0405-0607-0809-0A0B0C0D2B18"
+//写设备信息特征
+#define kSetDeviceInfoUUID @"FFF1"
+//通知设备信息特征
+#define kNotifyDeviceInfoUUID @"FFF2"
+
+@interface TlwBleManager ()<CBCentralManagerDelegate,CBPeripheralDelegate,TlwMouseDelegate>
+@property(nonatomic, strong) CBCentralManager *centralManager;///蓝牙管理
+@property(nonatomic, strong) CBPeripheral *connectPeripheral;//连接的设备
+@property(nonatomic, strong) CBCharacteristic *deviceInfoCha;//设备信息特征
+@property (nonatomic,strong) NSMutableData *recordData;//录音的数据
+@end
+
+@implementation TlwBleManager
++(TlwBleManager *)shared{
+    static TlwBleManager *manager = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        manager = [[TlwBleManager alloc]init];
+    });
+    return manager;
+}
+
+- (instancetype)init{
+    self = [super init];
+    if (self) {
+        self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
+    }
+    return self;
+}
+
+#pragma mark - CBCentralManagerDelegate
+- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
+    [self showLog:[NSString stringWithFormat:@"蓝牙状态 = %@",@(central.state)]];
+    switch (central.state) {
+        case CBManagerStateUnknown:
+            
+            break;
+        case CBManagerStateResetting:
+            
+            break;
+        case CBManagerStateUnsupported:
+            
+            break;
+        case CBManagerStateUnauthorized:
+            
+            break;
+        case CBManagerStatePoweredOff:
+            
+            break;
+        case CBManagerStatePoweredOn:
+            [self autoConnectDevice];
+            break;
+    }
+}
+
+- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
+//    [self showLog:[NSString stringWithFormat:@"扫描到设备 = %@, data = %@",peripheral,advertisementData]];
+    if(self.discoverDevice){
+        self.discoverDevice(peripheral);
+    }
+}
+
+- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
+    [self showLog:[NSString stringWithFormat:@"连接成功 name = %@",peripheral.name]];
+    if(self.didConnectDevice){
+        self.didConnectDevice(peripheral);
+    }
+    peripheral.delegate = self;
+    [peripheral discoverServices:nil];
+    
+    [[NSNotificationCenter defaultCenter] postNotificationName:kDidConnectBleNotification object:nil];
+}
+-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
+    [self showLog:[NSString stringWithFormat:@"断开连接。。。%@",error.description]];
+    [[NSNotificationCenter defaultCenter] postNotificationName:kDidDisconnectBleNotification object:nil];
+    if(self.didDisConnectDevice){
+        self.didDisConnectDevice(peripheral);
+    }
+//    [self aiMouseConnectStateDidChange:false];
+    [[TlwMouse shared] didDisconnect];
+}
+- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
+    [self showLog:[NSString stringWithFormat:@"连接失败。。。%@",error.description]];
+}
+#pragma mark - CBPeripheralDelegate
+//发现服务
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
+    for (CBService *service in peripheral.services){
+        [peripheral discoverCharacteristics:nil forService:service];
+    }
+}
+// 发现特征
+- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
+    for (CBCharacteristic *characteristic in service.characteristics){
+        if ([characteristic.UUID.UUIDString isEqualToString:kAiUUID]) {
+            //语音特征
+            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
+        }
+        else if ([characteristic.UUID.UUIDString isEqualToString:kNotifyDeviceInfoUUID]) {
+            //通知设备信息特征
+            [peripheral setNotifyValue:YES forCharacteristic:characteristic];
+        }
+        else if ([characteristic.UUID.UUIDString isEqualToString:kSetDeviceInfoUUID]) {
+            //设备信息特征
+            self.deviceInfoCha = characteristic;
+            //切换代理
+            [TlwMouse shared].delegate = self;
+            //切换当前连接类型
+            [[TlwMouse shared] switchConnect:TlwConnectType_bluetooth];
+        }
+    }
+}
+//收到蓝牙数据
+- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
+    NSData *data = characteristic.value;
+    if([characteristic.UUID.UUIDString isEqualToString:kAiUUID]){
+        //AI数据
+        [[TlwMouse shared] sendAiDataToSdk:data];
+    }
+    else if([characteristic.UUID.UUIDString isEqualToString:kNotifyDeviceInfoUUID]){
+        //设备信息数据
+        [[TlwMouse shared] sendDeviceDataToSdk:data];
+    }
+}
+
+#pragma mark - AiDelegate
+
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备
+/// - Parameter data: 数据
+-(void)tlwMouseSendDeviceInfoData:(NSData *)data{
+    [self.connectPeripheral writeValue:data forCharacteristic:self.deviceInfoCha type:CBCharacteristicWriteWithResponse];
+}
+
+/// 鼠标按键指令回调
+/// - Parameter data: 数据。
+/// 前  单击0x06, 按下0x13(开始录音)抬起0x14(结束录音)
+/// 中  单击0x03, 按下0x11(开始录音)抬起0x12(结束录音)
+/// 后  第一次单击0x01, 第二次单击0x02(结束录音) , 按下0x09(开始录音)抬起0x10(结束录音)
+/// 其他未定义
+- (void)tlwMouseSendCmdData:(NSData *)data{
+    uint8 cmd;
+    [data getBytes:&cmd length:1];
+    
+    if(cmd == 0x13 || cmd == 0x11 || cmd == 0x01 ||cmd == 0x09){
+        [self showLog:@"开始录音"];
+        self.recordData = [[NSMutableData alloc] init];
+    }
+    else if(cmd == 0x14 || cmd == 0x12 || cmd == 0x02 ||cmd == 0x10){
+        [self showLog:@"结束录音"];
+        [self finishRecored:self.recordData];
+    }
+}
+
+/// 录音回调
+/// - Parameter data: pcm格式语音数据
+- (void)tlwMouseSendRecordData:(NSData *)data{
+    [self.recordData appendData:data];
+}
+/// 输出日志
+- (void)aiMouseAddLog:(NSString *)log { 
+//    [self showLog:log];
+}
+
+// 连接状态改变
+- (void)aiMouseConnectStateDidChange:(BOOL)connected{
+    if (connected) {
+        [[NSNotificationCenter defaultCenter] postNotificationName:kDidConnectBleNotification object:nil];
+    }
+    else{
+        [[NSNotificationCenter defaultCenter] postNotificationName:kDidDisconnectBleNotification object:nil];
+    }
+}
+#pragma mark - public
+/// 连接设备
+- (void)startScanDevice{
+    [self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : [NSNumber numberWithBool:NO]}];
+}
+
+/// 扫描设备
+- (void)stopScanDevice{
+    [self.centralManager stopScan];
+}
+
+/// 连接设备
+- (void)connectPeripheral:(CBPeripheral *)peripheral{
+    self.connectPeripheral = peripheral;
+    [self.centralManager connectPeripheral:peripheral options:@{CBConnectPeripheralOptionNotifyOnConnectionKey : [NSNumber numberWithBool:YES]}];
+}
+
+/// 获取设备信息
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo * _Nonnull, NSError * _Nonnull))finished{
+    [[TlwMouse shared] getDeviceInfo:finished];
+}
+
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] setDPI:value completation:finished];
+}
+
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] openRecord:finished];
+}
+
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] closeRecord:finished];
+}
+#pragma mark - other
+//自动连接,获取已配对的蓝牙,取第一个连接
+- (void)autoConnectDevice{
+    //如果是已连接或者正在连接,则不再重连
+    if (self.connectPeripheral && (self.connectPeripheral.state == CBPeripheralStateConnected || self.connectPeripheral.state == CBPeripheralStateConnecting)) {
+        return;
+    }
+    NSArray<CBPeripheral *> *devices = [self.centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
+    if (devices.count > 0) {
+        CBPeripheral *peripheral = devices[0];
+        [self showLog:[NSString stringWithFormat:@"匹配过的设备%@",peripheral.name]];
+        [self connectPeripheral:peripheral];
+    }
+    // 10s自动重连
+    [self performSelector:@selector(autoConnectDevice) withObject:nil afterDelay:10];
+}
+- (void)showLog:(NSString *)log{
+    NSLog(@"%@",log);
+}
+
+//将录音数据保存
+- (void)finishRecored:(NSData *)data{
+    NSString *doc = NSTemporaryDirectory();
+    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc];
+    NSError *err;
+    [data writeToFile:fullPath options:NSDataWritingAtomic error:&err];
+    if (err) {
+        [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]];
+    }
+    else{
+        [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]];
+    }
+}
+
+- (NSString *)dataToHexString:(NSData *)data {
+    NSUInteger length = data.length;
+    NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
+    const unsigned char *byte = data.bytes;
+    for (int i = 0; i < length; i++, byte++) {
+        [result appendFormat:@"%02X", *byte];
+    }
+    return result;
+}
+@end
diff --git a/MouseSdk/MouseSdk/Tlw/TlwDeviceManger.h b/MouseSdk/MouseSdk/Tlw/TlwDeviceManger.h
new file mode 100644 (file)
index 0000000..c3641a0
--- /dev/null
@@ -0,0 +1,36 @@
+//
+//  TlwDeviceManger.h
+//  MouseSdk
+//
+//  Created by incar on 2024/3/30.
+//
+
+#import <Foundation/Foundation.h>
+#import "TlwMouseSdk.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwDeviceManger : NSObject
+@property (nonatomic, readonly) TlwConnectType connectType;//连接类型
+@property (nonatomic,strong)void (^connectStateCallBack)(TlwConnectType connectType,BOOL isConnect);///是否连接成功
+
++(TlwDeviceManger *)shared;
+
+/// 切换连接类型
+/// - Parameter type: 连接类型
+- (void)switchConnect:(TlwConnectType)type;
+
+/// 获取设备信息
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))finished;
+
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/Tlw/TlwDeviceManger.m b/MouseSdk/MouseSdk/Tlw/TlwDeviceManger.m
new file mode 100644 (file)
index 0000000..5f5c3b4
--- /dev/null
@@ -0,0 +1,125 @@
+//
+//  TlwDeviceManger.m
+//  MouseSdk
+//
+//  Created by incar on 2024/3/30.
+//
+
+#import "TlwDeviceManger.h"
+#import "TlwHidManager.h"
+#import "TlwBleManager.h"
+
+@interface TlwDeviceManger()
+
+@property (nonatomic,strong) TlwBleManager *ble;
+@property (nonatomic,strong) TlwHidManager *hid;
+@end
+
+@implementation TlwDeviceManger
++(TlwDeviceManger *)shared{
+    static TlwDeviceManger *manager = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        manager = [[TlwDeviceManger alloc]init];
+    });
+    return manager;
+}
+
+- (instancetype)init{
+    self = [super init];
+    if (self) {
+        [[TlwMouse shared] setLogLevel:TlwMouseLogLevel_info];
+        [self setupBle];
+        [self setupHid];
+    }
+    return self;
+}
+
+-(TlwConnectType)connectType{
+    return [TlwMouse shared].connectType;
+}
+
+- (void)switchConnect:(TlwConnectType)type{
+    [[TlwMouse shared] switchConnect:type];
+    
+    if ([TlwMouse shared].connectType == TlwConnectType_bluetooth) {
+        [self setupBle];
+    }
+    else{
+        [self setupHid];
+    }
+}
+
+- (void)setupHid{
+    self.hid = [TlwHidManager shared];
+    __weak typeof(self) ws = self;
+    self.hid.connectStateCallBack = ^(BOOL isConnnect) {
+        if(ws.connectStateCallBack)ws.connectStateCallBack(TlwConnectType_hid,isConnnect);
+    };
+}
+
+- (void)setupBle{
+    self.ble = [TlwBleManager shared];
+    __weak typeof(self) ws = self;
+    self.ble.didConnectDevice = ^(CBPeripheral *peripheral) {
+        if(ws.connectStateCallBack)ws.connectStateCallBack(TlwConnectType_bluetooth,true);
+    };
+    self.ble.didDisConnectDevice = ^(CBPeripheral *peripheral) {
+        if(ws.connectStateCallBack)ws.connectStateCallBack(TlwConnectType_bluetooth,false);
+    };
+    [self setupBleNotification];
+}
+
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))finished{
+    if ([TlwMouse shared].connectType == TlwConnectType_bluetooth) {
+        [self.ble getDeviceInfo:finished];
+    }
+    else{
+        [self.hid getDeviceInfo:finished];
+    }
+}
+
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if ([TlwMouse shared].connectType == TlwConnectType_bluetooth) {
+        [self.ble setDPI:value completation:finished];
+    }
+    else{
+        [self.hid setDPI:value completation:finished];
+    }
+}
+
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if ([TlwMouse shared].connectType == TlwConnectType_bluetooth) {
+        [self.ble openRecord:finished];
+    }
+    else{
+        [self.hid openRecord:finished];
+    }
+}
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    if ([TlwMouse shared].connectType == TlwConnectType_bluetooth) {
+        [self.ble closeRecord:finished];
+    }
+    else{
+        [self.hid closeRecord:finished];
+    }
+}
+#pragma mark - notication
+- (void)setupBleNotification{
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidConnect:) name:kDidConnectBleNotification object:nil];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bleDidDisconnect:) name:kDidDisconnectBleNotification object:nil];
+}
+
+- (void)bleDidConnect:(NSNotification *)notification{
+    if(self.connectStateCallBack)self.connectStateCallBack(TlwConnectType_bluetooth,true);
+}
+
+- (void)bleDidDisconnect:(NSNotification *)notification{
+    if(self.connectStateCallBack)self.connectStateCallBack(TlwConnectType_bluetooth,false);
+    [[TlwMouse shared] switchConnect:TlwConnectType_hid];
+}
+
+@end
diff --git a/MouseSdk/MouseSdk/Tlw/TlwHidManager.h b/MouseSdk/MouseSdk/Tlw/TlwHidManager.h
new file mode 100644 (file)
index 0000000..76407ec
--- /dev/null
@@ -0,0 +1,33 @@
+//
+//  TlwHidManager.h
+//  MouseSdk
+//
+//  Created by incar on 2024/3/26.
+//
+
+#import <Foundation/Foundation.h>
+#import "TlwMouseSdk.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwHidManager : NSObject
+@property (nonatomic,strong)void (^connectStateCallBack)(BOOL isConnnect);/// 设备是否连上
+
++(TlwHidManager *)shared;
+
+- (void)showLog:(NSString *)log;
+
+/// 获取设备信息
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo *deviceInfo,NSError *error))finished;
+
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/Tlw/TlwHidManager.m b/MouseSdk/MouseSdk/Tlw/TlwHidManager.m
new file mode 100644 (file)
index 0000000..721653a
--- /dev/null
@@ -0,0 +1,256 @@
+//
+//  HidManager.m
+//  MouseSdk
+//
+//  Created by incar on 2024/3/26.
+//
+
+#import "TlwHidManager.h"
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#import "Config.h"
+
+
+
+//供应商id
+//const long vendorID = 0x6675;
+//const long productID = 0x8601;
+
+@interface TlwHidManager ()<TlwMouseDelegate>
+
+@property(nonatomic, assign) IOHIDManagerRef hidManager;///hid管理
+@property(nonatomic, assign) IOHIDDeviceRef deviceInfoCha;//设备信息通道
+@property (nonatomic,strong) NSMutableData *recordData;//录音的数据
+@end
+
+@implementation TlwHidManager
++(TlwHidManager *)shared{
+    static TlwHidManager *manager = nil;
+    static dispatch_once_t oncePredicate;
+    dispatch_once(&oncePredicate, ^{
+        manager = [[TlwHidManager alloc]init];
+    });
+    return manager;
+}
+
+- (instancetype)init{
+    self = [super init];
+    if (self) {
+        [self initHid];
+        [self open];
+    }
+    return self;
+}
+
+- (void)initHid{
+
+}
+
+- (void)open{
+    self.hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+    IOHIDManagerScheduleWithRunLoop(self.hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    IOReturn ret = IOHIDManagerOpen(self.hidManager, 0L);
+    if (ret != kIOReturnSuccess) {
+        NSLog(@"打开设备失败!");
+    }else{
+        NSLog(@"打开设备成功!");
+    }
+    //设置匹配设备
+    //供应商id
+    const long vendorID = 0x10D6;
+    //产品id
+    const long productID = 0xB008;
+    NSMutableDictionary* dict= [NSMutableDictionary dictionary];
+    [dict setValue:[NSNumber numberWithLong:productID] forKey:[NSString stringWithCString:kIOHIDProductIDKey encoding:NSUTF8StringEncoding]];
+    [dict setValue:[NSNumber numberWithLong:vendorID] forKey:[NSString stringWithCString:kIOHIDVendorIDKey encoding:NSUTF8StringEncoding]];
+    IOHIDManagerSetDeviceMatching(self.hidManager, (__bridge CFMutableDictionaryRef)dict);
+    //设置匹配到设备回调
+    IOHIDManagerRegisterDeviceMatchingCallback(self.hidManager, &hidAppearDevice, NULL);
+    //设置移除设备回调
+    IOHIDManagerRegisterDeviceRemovalCallback(self.hidManager, &hidDisAppearDevice, NULL);
+}
+
+- (void)close{
+    IOReturn ret = IOHIDManagerClose(self.hidManager, 0);
+    if (ret != kIOReturnSuccess) {
+        NSLog(@"关闭设备失败!");
+    }else{
+        NSLog(@"关闭设备成功!");
+    }
+}
+
+#pragma mark - Hid CallBack
+/// 匹配到设备回调
+static void hidAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
+    [[TlwHidManager shared] showLog:[NSString stringWithFormat:@"发现设备 %@\n ",inIOHIDDeviceRef]];
+        [TlwHidManager shared].deviceInfoCha = inIOHIDDeviceRef;
+        [[TlwHidManager shared] connectDevice];
+        //注册消息回调
+        unsigned char InputBuffer[64];
+        IOHIDDeviceRegisterInputReportCallback(inIOHIDDeviceRef, InputBuffer, sizeof(InputBuffer), hidReceiveData, NULL);
+        IOReturn result = IOHIDDeviceOpen(inIOHIDDeviceRef, kIOHIDOptionsTypeNone);
+        if (result != kIOReturnSuccess) {
+            NSLog([NSString stringWithFormat:@"打开设备信息失败! %d",result]);
+        }else{
+            NSLog(@"打开设备信息成功!");
+        }
+        CFRetain(inIOHIDDeviceRef);
+        IOHIDDeviceScheduleWithRunLoop(inIOHIDDeviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+}
+
+/// 设备移除回调
+static void hidDisAppearDevice(void *inContext,IOReturn inResult,void *inSender,IOHIDDeviceRef inIOHIDDeviceRef) {
+    [[TlwHidManager shared] showLog:[NSString stringWithFormat:@"拔出设备 %@ ",inIOHIDDeviceRef]];
+    uint32_t PrimaryUsagePage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsagePageKey));
+    uint32_t PrimaryUsage = IOHIDDevGetUInt32Property(inIOHIDDeviceRef, CFSTR(kIOHIDPrimaryUsageKey));
+
+    if(PrimaryUsagePage == 1 && PrimaryUsage == 0){
+        [TlwHidManager shared].deviceInfoCha = nil;
+        [[TlwMouse shared] didDisconnect];
+    }
+}
+
+static void hidReceiveData(void* context, IOReturn result, void* sender, IOHIDReportType type, uint32_t reportID, uint8_t *report,CFIndex reportLength){
+    if (report[0] == 0x01 || report[0] == 0x02) {
+        //鼠标键盘活动事件
+        return;
+    }
+    [[TlwHidManager shared] showLog:[NSString stringWithFormat:@"[receive] 处理前 %@",[[TlwHidManager shared] dataToHexString:[[NSData alloc] initWithBytes:report length:reportLength]]]];
+    
+    NSInteger len = get_data_len(report, reportLength);
+    NSData *data = [[NSData alloc] initWithBytes:report length:len];
+    [[TlwMouse shared] sendDeviceDataToSdk:data];
+}
+
+#pragma mark - AiDelegate
+/// 设备信息数据回调,需要将此数据通过设备信息特征转发给蓝牙设备
+/// - Parameter data: 数据
+-(void)tlwMouseSendDeviceInfoData:(NSData *)data{
+    [self sendData:data toDevice:self.deviceInfoCha];
+}
+
+/// 鼠标按键指令回调
+/// - Parameter data: 数据。
+/// 前  单击0x06, 按下0x13(开始录音)抬起0x14(结束录音)
+/// 中  单击0x03, 按下0x11(开始录音)抬起0x12(结束录音)
+/// 后  第一次单击0x01, 第二次单击0x02(结束录音) , 按下0x09(开始录音)抬起0x10(结束录音)
+/// 其他未定义
+- (void)tlwMouseSendCmdData:(NSData *)data{
+    uint8 cmd;
+    [data getBytes:&cmd length:1];
+    
+    if(cmd == 0x13 || cmd == 0x11 || cmd == 0x01 ||cmd == 0x09){
+        [self showLog:@"开始录音"];
+        self.recordData = [[NSMutableData alloc] init];
+    }
+    else if(cmd == 0x14 || cmd == 0x12 || cmd == 0x02 ||cmd == 0x10){
+        [self showLog:@"结束录音"];
+        [self finishRecored:self.recordData];
+    }
+}
+/// 录音回调
+/// - Parameter data: pcm格式语音数据
+- (void)tlwMouseSendRecordData:(NSData *)data{
+    [self.recordData appendData:data];
+}
+/// 输出日志
+- (void)aiMouseAddLog:(NSString *)log {
+//    [self showLog:log];
+}
+
+// 连接状态改变
+- (void)aiMouseConnectStateDidChange:(BOOL)connected{
+    [TlwHidManager shared].connectStateCallBack(connected);
+}
+
+#pragma mark - public
+/// 获取设备信息
+- (void)getDeviceInfo:(void (^)(TlwDeviceInfo * _Nonnull, NSError * _Nonnull))completation{
+    [[TlwMouse shared] getDeviceInfo:completation];
+}
+/// setDPI
+- (void)setDPI:(int)value completation:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] setDPI:value completation:finished];
+}
+/// 打开录音
+- (void)openRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] openRecord:finished];
+}
+/// 停止录音
+- (void)closeRecord:(void (^)(BOOL isSuccess,NSError *error))finished{
+    [[TlwMouse shared] closeRecord:finished];
+}
+
+#pragma mark - other
+-(void)connectDevice{
+    //设置代理
+    [TlwMouse shared].delegate = self;
+    //切换当前连接类型
+    [[TlwMouse shared] switchConnect:TlwConnectType_hid];
+}
+
+/// 发送数据给设备
+-(void)sendData:(NSData *)data toDevice:(IOHIDDeviceRef)device{
+    if(device == nil){
+        [self showLog:@"请先连上设备"];
+        return;
+    }
+    
+    IOReturn ret = IOHIDDeviceSetReport(device, kIOHIDReportTypeOutput, 0, data.bytes, data.length);
+    if (ret != kIOReturnSuccess) {
+        [self showLog:[NSString stringWithFormat:@"数据发送失败! code = %d",ret]];
+    }else{
+        [self showLog:@"数据发送成功!"];
+    }
+}
+
+//将录音数据保存
+- (void)finishRecored:(NSData *)data{
+    NSString *doc = NSTemporaryDirectory();
+    NSString *fullPath = [NSString stringWithFormat:@"%@1.pcm",doc];
+    NSError *err;
+    [data writeToFile:fullPath options:NSDataWritingAtomic error:&err];
+    if (err) {
+        [self showLog:[NSString stringWithFormat:@"录音保存失败 %@",err.description]];
+    }
+    else{
+        [self showLog:[NSString stringWithFormat:@"录音保存成功 path = %@",fullPath]];
+    }
+}
+- (void)showLog:(NSString *)log{
+    NSLog(@"%@",log);
+}
+
+
+/// 实际到数据长度
+static inline int get_data_len(uint8_t *buf, u_long len) {
+    if (len <= 0 ) return 0;
+    int url_len = 0;
+    while (buf[url_len] != 0x00) url_len++;
+    return url_len;
+}
+
+/// 获取对应属性值
+static uint32_t IOHIDDevGetUInt32Property(IOHIDDeviceRef inIOHIDDeviceRef, CFStringRef inKey) {
+    uint32_t outValue = 0;
+    if (inIOHIDDeviceRef != NULL) {
+        CFTypeRef tCFTypeRef = IOHIDDeviceGetProperty(inIOHIDDeviceRef, inKey);
+        if (tCFTypeRef != NULL && (CFNumberGetTypeID() == CFGetTypeID(tCFTypeRef))  ) {
+            Boolean result = CFNumberGetValue((CFNumberRef) tCFTypeRef, kCFNumberSInt32Type, &outValue);
+            if (!result) {
+                [[TlwHidManager shared] showLog:[NSString stringWithFormat:@"get %@ failed.", inKey]];
+            }
+        }
+    }
+    return outValue;
+}
+- (NSString *)dataToHexString:(NSData *)data {
+    NSUInteger length = data.length;
+    NSMutableString *result = [NSMutableString stringWithCapacity:length * 2];
+    const unsigned char *byte = data.bytes;
+    for (int i = 0; i < length; i++, byte++) {
+        [result appendFormat:@"%02X", *byte];
+    }
+    return result;
+}
+@end
diff --git a/MouseSdk/MouseSdk/TlwScanVC.h b/MouseSdk/MouseSdk/TlwScanVC.h
new file mode 100644 (file)
index 0000000..4be9c77
--- /dev/null
@@ -0,0 +1,16 @@
+//
+//  TlwScanVC.h
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwScanVC : NSViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/TlwScanVC.m b/MouseSdk/MouseSdk/TlwScanVC.m
new file mode 100644 (file)
index 0000000..603c230
--- /dev/null
@@ -0,0 +1,124 @@
+//
+//  TlwScanVC.m
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import "TlwScanVC.h"
+
+#import "ScanCell.h"
+#import "TlwDeviceManger.h"
+@interface TlwScanVC ()<NSTableViewDelegate,NSTableViewDataSource>
+@property (nonatomic,strong) NSTableView *tableView;
+@property (nonatomic,strong) NSMutableArray<CBPeripheral *> *scanArray;
+@end
+
+@implementation TlwScanVC
+-(NSMutableArray<CBPeripheral *> *)scanArray{
+    if (_scanArray == nil) {
+        _scanArray = [[NSMutableArray alloc] init];
+    }
+    return _scanArray;
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do view setup here.
+    [self setupUI];
+    [self setupBleManger];
+}
+- (void)loadView{
+    self.view = [[NSView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
+}
+
+- (void)setupUI{
+    CALayer *viewLayer = [CALayer layer];
+    NSView *backgroundView= [[NSView alloc]init];
+    [backgroundView setWantsLayer:YES];
+    [backgroundView setLayer:viewLayer];
+    backgroundView.layer.backgroundColor = [NSColor greenColor].CGColor;
+    [backgroundView setNeedsDisplay:YES];
+    [self.view addSubview:backgroundView];
+    
+    //实现tableview的滚动效果
+    NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:self.view.bounds];
+    scrollView.backgroundColor = [NSColor redColor];
+    [scrollView setDrawsBackground:NO];//不画背景(背景默认画成白色)
+    [scrollView setHasVerticalScroller:YES];//有垂直滚动条
+    //[_tableContainer setHasHorizontalScroller:YES];  //有水平滚动条
+    scrollView.autohidesScrollers = YES;//自动隐藏滚动条(滚动的时候出现)
+    [self.view addSubview:scrollView];
+    
+    self.tableView = [[NSTableView alloc] initWithFrame:scrollView.bounds];
+    [scrollView setDocumentView:self.tableView];
+    
+    NSTableColumn *column1 = [[NSTableColumn alloc] initWithIdentifier:@"columnFrist"];
+    column1.title = @"设备名称";
+    [column1 setWidth:self.tableView.bounds.size.width];
+    [self.tableView addTableColumn:column1];
+    
+//    self.tableView.focusRingType = NSFocusRingTypeNone;//tableview获得焦点时的风格
+//    self.tableView.selectionHighlightStyle = NSTableViewSelectionHighlightStyleRegular;//行高亮的风格
+//    self.tableView.backgroundColor = [NSColor greenColor];
+//    self.tableView.usesAlternatingRowBackgroundColors = YES; //背景颜色的交替,一行白色,一行灰色。设置后,原来设置的 backgroundColor 就无效了。
+    self.tableView.delegate = self;
+    self.tableView.dataSource = self;
+    self.tableView.gridStyleMask = NSTableViewSolidHorizontalGridLineMask;
+}
+
+- (void)setupBleManger{
+//    TlwBleManager *manager = [TlwDeviceManger shared].ble;
+//    __weak typeof(self) weakSelf= self;
+//    manager.discoverDevice = ^(CBPeripheral *peripheral){
+//        if ([weakSelf.scanArray containsObject:peripheral]){
+//            return;
+//        }
+//        if(peripheral.name.length == 0){
+//            return;
+//        }
+//        [weakSelf.scanArray addObject:peripheral];
+//        [weakSelf.tableView reloadData];
+//    };
+//    manager.didConnectDevice = ^(CBPeripheral * _Nonnull peripheral) {
+//        [weakSelf connectSuccess];
+//    };
+//    [manager startScanDevice];
+}
+
+- (void)tapDevice:(CBPeripheral *)peripheral{
+//    [[TlwDeviceManger shared].ble connectPeripheral:peripheral];
+}
+
+- (void)connectSuccess{
+//    [[TlwDeviceManger shared].ble stopScanDevice];
+//    dispatch_async(dispatch_get_main_queue(), ^{
+//         NSAlert *alert = [NSAlert new];
+//         [alert addButtonWithTitle:@"确定"];
+//         [alert setMessageText:@"连接成功"];
+//         [alert setAlertStyle:NSAlertStyleInformational];
+//         [alert beginSheetModalForWindow:[self.view window] completionHandler:^(NSModalResponse returnCode) {
+//             [self dismissViewController:self];
+//         }];
+//    });
+}
+#pragma mark - NSTableViewDelegate,NSTableViewDataSource
+-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView{
+    return self.scanArray.count;
+}
+
+-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
+    ScanCell *cell = [[ScanCell alloc] initWithFrame:CGRectMake(0, 0, self.tableView.bounds.size.width, 44)];
+    cell.peripheral = self.scanArray[row];
+    __weak typeof(self) weakSelf = self;
+    cell.connecBtnActionCallBack = ^(CBPeripheral * _Nonnull peripheral) {
+        [weakSelf tapDevice:peripheral];
+    };
+    return cell;
+}
+-(CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row{
+    return 44;
+}
+
+@end
+
diff --git a/MouseSdk/MouseSdk/TlwScanVC.xib b/MouseSdk/MouseSdk/TlwScanVC.xib
new file mode 100644 (file)
index 0000000..76e313c
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11134" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="TlwScanVC">
+            <connections>
+                <outlet property="view" destination="Hz6-mo-xeY" id="0bl-1N-x8E"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customView id="Hz6-mo-xeY">
+            <rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+        </customView>
+    </objects>
+</document>
diff --git a/MouseSdk/MouseSdk/TlwVC.h b/MouseSdk/MouseSdk/TlwVC.h
new file mode 100644 (file)
index 0000000..e5f0f4d
--- /dev/null
@@ -0,0 +1,16 @@
+//
+//  TlwVC.h
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import <Cocoa/Cocoa.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TlwVC : NSViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/MouseSdk/MouseSdk/TlwVC.m b/MouseSdk/MouseSdk/TlwVC.m
new file mode 100644 (file)
index 0000000..03659fb
--- /dev/null
@@ -0,0 +1,192 @@
+//
+//  TlwVC.m
+//  MouseSdk
+//
+//  Created by incar on 2024/8/21.
+//
+
+#import "TlwVC.h"
+#import "TlwScanVC.h"
+#import "TlwDeviceManger.h"
+
+@interface TlwVC ()
+@property(nonatomic,strong)NSTextField *typeLabel;///当前模式状态
+@property(nonatomic,strong)NSTextField *connectLabel;///连接状态
+@property(nonatomic,strong)NSTextField *authLabel;///鉴权状态
+@property(nonatomic,strong)NSTextField *deviceInfoLabel;///设备信息
+@end
+
+@implementation TlwVC
+
+//-(instancetype)init{
+//    if (self = [super init]) {
+//        
+//    }
+//    return self;
+//}
+
+-(void)dealloc{
+//    [[TlwDeviceManger shared] stop];
+}
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    // Do any additional setup after loading the view.
+    
+    [self setupUI];
+    [self setupTlwDeviceManger];
+}
+
+- (void)loadView{
+    self.view = [[NSView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
+}
+
+- (void)setupUI{
+    CALayer *viewLayer = [CALayer layer];
+    NSView *backgroundView= [[NSView alloc]init];
+    [backgroundView setWantsLayer:YES];
+    [backgroundView setLayer:viewLayer];
+    backgroundView.layer.backgroundColor = [NSColor greenColor].CGColor;
+    [backgroundView setNeedsDisplay:YES];
+    [self.view addSubview:backgroundView];
+    self.title = @"AI SDK";
+    {
+        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 80, 100, 45)];
+        btn.bordered = true;
+        btn.title = @"蓝牙扫描";
+        [btn setTarget:self];
+        [btn setAction:@selector(scanAction)];
+        btn.state = 1;
+        [self.view addSubview:btn];
+    }
+    {
+        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 180, 100, 45)];
+        btn.bordered = true;
+        btn.title = @"选择指令";
+        [btn setTarget:self];
+        [btn setAction:@selector(showPopupMenu:)];
+        
+        btn.state = 1;
+        [self.view addSubview:btn];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 80, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"连接模式:--";
+        label.backgroundColor = NSColor.clearColor;
+        self.typeLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 120, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"连接状态:未连接";
+        label.backgroundColor = NSColor.clearColor;
+        self.connectLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 160, 300, 35)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"鉴权状态:unknow";
+        label.backgroundColor = NSColor.clearColor;
+        self.authLabel = label;
+        [self.view addSubview:label];
+    }
+    {
+        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 260, 300, 100)];
+        label.editable = NO;
+        label.bordered = NO;
+        label.stringValue = @"";
+        label.backgroundColor = NSColor.clearColor;
+        self.deviceInfoLabel = label;
+        [self.view addSubview:label];
+    }
+}
+
+#pragma mark - action
+- (void)scanAction{
+    TlwScanVC *vc = [[TlwScanVC alloc] initWithNibName:nil bundle:nil];
+    [self presentViewControllerAsModalWindow:vc];
+}
+
+- (void)connectStateDidChange:(TlwConnectType)type connectState:(BOOL)isConnected{
+    dispatch_async(dispatch_get_main_queue(), ^{
+        if(isConnected){
+            self.typeLabel.stringValue = type == TlwConnectType_bluetooth ?@"连接模式:蓝牙":@"连接模式:hid";
+            self.connectLabel.stringValue = isConnected?@"连接状态:已连接":@"连接状态:未连接";
+        }
+        else{
+            self.typeLabel.stringValue = @"连接模式:--";
+            self.connectLabel.stringValue = @"连接状态:未连接";
+        }
+    });
+}
+
+- (void)setupTlwDeviceManger{
+    TlwDeviceManger *manager = [TlwDeviceManger shared];
+//    [manager start];
+    __weak typeof(self) ws = self;
+    manager.connectStateCallBack = ^(TlwConnectType type,BOOL isConnected) {
+        [ws connectStateDidChange:type connectState:isConnected];
+    };
+}
+
+
+- (void)showPopupMenu:(NSButton *)sender {
+    NSMenu *popupMenu = [[NSMenu alloc] initWithTitle:@"Popup Menu"];
+    NSMenuItem *item1 = [[NSMenuItem alloc] initWithTitle:@"语音开启" action:@selector(voiceStart:) keyEquivalent:@"1"];
+    NSMenuItem *item2 = [[NSMenuItem alloc] initWithTitle:@"语音停止" action:@selector(voiceStop:) keyEquivalent:@"2"];
+    NSMenuItem *item3 = [[NSMenuItem alloc] initWithTitle:@"设置DPI" action:@selector(setDPI:) keyEquivalent:@"3"];
+    NSMenuItem *item4 = [[NSMenuItem alloc] initWithTitle:@"鼠标状态查询" action:@selector(searchState:) keyEquivalent:@"3"];
+//    NSMenuItem *item5 = [[NSMenuItem alloc] initWithTitle:@"向鼠标告知APP下载地址" action:@selector(setDownLoadURL:) keyEquivalent:@"3"];
+//    NSMenuItem *item8 = [[NSMenuItem alloc] initWithTitle:@":读取鼠标序列号" action:@selector(getSN:) keyEquivalent:@"3"];
+//    NSMenuItem *item9 = [[NSMenuItem alloc] initWithTitle:@":发送弹UR开关命令" action:@selector(sendUR:) keyEquivalent:@"3"];
+
+    [popupMenu addItem:item1];
+    [popupMenu addItem:item2];
+    [popupMenu addItem:item3];
+    [popupMenu addItem:item4];
+
+    NSPoint buttonLocation = [sender convertPoint:NSZeroPoint toView:self.view];
+    [popupMenu popUpMenuPositioningItem:nil atLocation:buttonLocation inView:self.view];
+}
+
+- (void)voiceStart:(NSMenuItem*)sender {
+    [[TlwDeviceManger shared] openRecord:^(BOOL isSuccess, NSError * _Nonnull error) {
+        
+    }];
+}
+- (void)voiceStop:(NSMenuItem*)sender {
+    [[TlwDeviceManger shared] closeRecord:^(BOOL isSuccess, NSError * _Nonnull error) {
+        
+    }];
+}
+- (void)setDPI:(NSMenuItem*)sender {
+    [[TlwDeviceManger shared] setDPI:2 completation:^(BOOL isSuccess, NSError * _Nonnull error) {
+        
+    }];
+}
+- (void)searchState:(NSMenuItem*)sender {
+    [[TlwDeviceManger shared] getDeviceInfo:^(TlwDeviceInfo *deviceInfo, NSError *error) {
+//        if (deviceInfo) {
+//            NSString *info = deviceInfo.description;
+//            dispatch_async(dispatch_get_main_queue(), ^{
+//                self.deviceInfoLabel.stringValue = info;
+//            });
+//        }
+//        else{
+//            NSString *info = [NSString stringWithFormat:@"error == %@",error.description];
+//            NSLog(@"%@",info);
+//            dispatch_async(dispatch_get_main_queue(), ^{
+//                self.deviceInfoLabel.stringValue = info;
+//            });
+//        }
+    }];
+}
+
+@end
index 6f1ef58..3a0fec8 100644 (file)
@@ -6,14 +6,12 @@
 //
 
 #import "ViewController.h"
+#import "FrkVC.h"
+#import "TlwVC.h"
 #import "ScanVC.h"
-#import "DeviceManger.h"
+#import "TlwHidManager.h"
 
 @interface ViewController ()
-@property(nonatomic,strong)NSTextField *typeLabel;///当前模式状态
-@property(nonatomic,strong)NSTextField *connectLabel;///连接状态
-@property(nonatomic,strong)NSTextField *authLabel;///鉴权状态
-@property(nonatomic,strong)NSTextField *deviceInfoLabel;///设备信息
 @end
 
 @implementation ViewController
@@ -23,7 +21,6 @@
     
     // Do any additional setup after loading the view.
     [self setupUI];
-    [self setupDeviceManger];
 }
 
 - (void)setRepresentedObject:(id)representedObject {
     {
         NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 80, 100, 45)];
         btn.bordered = true;
-        btn.title = @"蓝牙扫描";
+        btn.title = @"富瑞坤";
         [btn setTarget:self];
-        [btn setAction:@selector(scanAction)];
+        [btn setAction:@selector(frkAction)];
         btn.state = 1;
         [self.view addSubview:btn];
     }
     {
-        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 180, 100, 45)];
+        NSButton *btn = [[NSButton alloc]initWithFrame:NSMakeRect(50, self.view.bounds.size.height - 200, 100, 45)];
         btn.bordered = true;
-        btn.title = @"获取设备信息";
+        btn.title = @"泰凌微";
         [btn setTarget:self];
-        [btn setAction:@selector(geviceInfoAction)];
+        [btn setAction:@selector(tlwAction)];
         btn.state = 1;
         [self.view addSubview:btn];
     }
-    {
-        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 80, 300, 35)];
-        label.editable = NO;
-        label.bordered = NO;
-        label.stringValue = @"连接模式:--";
-        label.backgroundColor = NSColor.clearColor;
-        self.typeLabel = label;
-        [self.view addSubview:label];
-    }
-    {
-        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 120, 300, 35)];
-        label.editable = NO;
-        label.bordered = NO;
-        label.stringValue = @"连接状态:未连接";
-        label.backgroundColor = NSColor.clearColor;
-        self.connectLabel = label;
-        [self.view addSubview:label];
-    }
-    {
-        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 160, 300, 35)];
-        label.editable = NO;
-        label.bordered = NO;
-        label.stringValue = @"鉴权状态:unknow";
-        label.backgroundColor = NSColor.clearColor;
-        self.authLabel = label;
-        [self.view addSubview:label];
-    }
-    {
-        NSTextField *label = [[NSTextField alloc] initWithFrame:CGRectMake(180, self.view.bounds.size.height - 260, 300, 100)];
-        label.editable = NO;
-        label.bordered = NO;
-        label.stringValue = @"";
-        label.backgroundColor = NSColor.clearColor;
-        self.deviceInfoLabel = label;
-        [self.view addSubview:label];
-    }
 }
 
 #pragma mark - action
-- (void)scanAction{
-    ScanVC *vc = [[ScanVC alloc] initWithNibName:nil bundle:nil];
+- (void)frkAction{
+    FrkVC *vc = [[FrkVC alloc] initWithNibName:nil bundle:nil];
     [self presentViewControllerAsModalWindow:vc];
 }
-
-/// 获取设备信息
-- (void)geviceInfoAction{
-    [[DeviceManger shared] getDeviceInfo:^(AiMouseDeviceInfo *deviceInfo, NSError *error) {
-        if (deviceInfo) {
-            NSString *info = deviceInfo.description;
-            dispatch_async(dispatch_get_main_queue(), ^{
-                self.deviceInfoLabel.stringValue = info;
-            });
-        }
-        else{
-            NSString *info = [NSString stringWithFormat:@"error == %@",error.description];
-            NSLog(@"%@",info);
-            dispatch_async(dispatch_get_main_queue(), ^{
-                self.deviceInfoLabel.stringValue = info;
-            });
-        }
-    }];
-}
-- (void)showAuthResult:(BOOL)isSuccess{
-    dispatch_async(dispatch_get_main_queue(), ^{
-        self.authLabel.stringValue = isSuccess?@"鉴权状态:鉴权成功":@"鉴权状态:鉴权失败";
-        if(isSuccess){
-            [self geviceInfoAction];
-        }
-    });
-}
-- (void)connectStateDidChange:(AiMouseConnectType)type connectState:(BOOL)isConnected{
-    dispatch_async(dispatch_get_main_queue(), ^{
-        if(isConnected){
-            self.typeLabel.stringValue = type == AiMouseConnectType_bluetooth ?@"连接模式:蓝牙":@"连接模式:hid";
-            self.connectLabel.stringValue = isConnected?@"连接状态:已连接":@"连接状态:未连接";
-            [self geviceInfoAction];
-        }
-        else{
-            self.typeLabel.stringValue = @"连接模式:--";
-            self.connectLabel.stringValue = @"连接状态:未连接";
-        }
-    });
-}
-
-- (void)setupDeviceManger{
-    DeviceManger *manager = [DeviceManger shared];
-    __weak typeof(self) ws = self;
-    manager.authCallBack = ^(BOOL isSuccess) {
-        [ws showAuthResult:isSuccess];
-    };
-    manager.connectStateCallBack = ^(AiMouseConnectType type,BOOL isConnected) {
-        [ws connectStateDidChange:type connectState:isConnected];
-    };
+- (void)tlwAction{
+    [TlwHidManager shared];
+    return;
+    TlwVC *vc = [[TlwVC alloc] initWithNibName:nil bundle:nil];
+    [self presentViewControllerAsModalWindow:vc];
+    
 }
-
 @end