--- /dev/null
+//
+// 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
--- /dev/null
+
+#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
--- /dev/null
+//
+// Aiaa.h
+// AiMouseSdk
+//
+// Created by incar on 2024/4/27.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface AiTrippleModel : NSObject
+@property (nonatomic,strong) NSString *productKey;//产品秘钥
+@property (nonatomic,strong) NSString *deviceName;//设备名称
+@property (nonatomic,strong) NSString *timeStamp;//时间戳
+@property (nonatomic,strong) NSString *signmethod;//签名方法
+@property (nonatomic,strong) NSString *sign; //签名
+@property (nonatomic,strong) NSString *sonce;//用于签名的随机盐值
+@end
+
+NS_ASSUME_NONNULL_END
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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(@"[AiMouseSdk]%@",[NSString stringWithFormat:@"AiMouseSdk_v%@",self.version]);
+ }
+ return self;
+}
+
+-(NSString *)version{
+ return @"1.1.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
+
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
--- /dev/null
+//
+// 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
+
--- /dev/null
+/********************************************************************************************************
+ * @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
+
--- /dev/null
+/********************************************************************************************************
+ * @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_ */
archiveVersion = 1;
classes = {
};
- objectVersion = 56;
+ objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
};
};
buildConfigurationList = 18BF1DC52B81AB7C00B8D90F /* Build configuration list for PBXProject "MouseSdk" */;
- compatibilityVersion = "Xcode 14.0";
+ compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
btn.bordered = true;
btn.title = @"选择指令";
[btn setTarget:self];
- [btn setAction:@selector(geviceInfoAction)];
[btn setAction:@selector(showPopupMenu:)];
btn.state = 1;