MNQtTrainVC.m 100 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433
  1. //
  2. // MNQtTrainVC.m
  3. // jiaPei
  4. //
  5. // Created by EchoShacolee on 2018/4/3.
  6. // Copyright © 2018年 JCZ. All rights reserved.
  7. //
  8. /*
  9. 首次连接流程:app搜索到外设 连接发送指令kGetMNdevVersion获取终端版本。判断是否需要升级。
  10. 升级完成/不需要升级 app发送请求kGetDevNumAndStuCardOrder获取终端序列号,
  11. ---看UI吧。那就是整个流程
  12. 然后 过程人脸验证会根据 登录返回的 mnqFacePorcess 判断是否开启,
  13. */
  14. #import "MNQtTrainVC.h"
  15. #import "ScanVC.h"
  16. #import <CoreBluetooth/CoreBluetooth.h>
  17. #import <BaiduMapAPI_Utils/BMKUtilsComponent.h>//bd计算相关
  18. #import "DES3Util.h"
  19. #import "MNQDev.h"
  20. #import <AFHTTPSessionManager.h>
  21. #import "PrecautionsView.h"
  22. #import "StepImageView.h"
  23. #import "MBProgressHUD+DS.h"
  24. #define BLE_SEND_MAX_LEN 20
  25. #define BLE_SEND_PER_LEN 0x0384 //900(0x0384)==图片
  26. static NSString * const mnTableName = @"mnPeriod";
  27. static NSString * const kServiceUUID = @"6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
  28. static NSString * const kCharacteristicUUID_write = @"6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
  29. static NSString * const kCharacteristicUUID_reade = @"6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
  30. static NSString * const kGetDevNumAndStuCardOrder = @"be000383eded";
  31. static NSString * const kTakePhotoOrder = @"be000384eded";
  32. static NSString * const kGetPhotoData = @"be000497";
  33. static NSString * const kGetMNdevVersion = @"be00038aeded";
  34. @interface MNQtTrainVC ()<CBCentralManagerDelegate,CBPeripheralDelegate,CLLocationManagerDelegate,UITableViewDelegate,UITableViewDataSource>
  35. {
  36. NSDateFormatter *dateFormatter;
  37. NSMutableDictionary *minuteTrainDic;//分钟学时用参数 签到和断线重连时候初始化 分钟学时发送之前更新字典
  38. NSMutableDictionary *gatherTrainDic;//汇总学时用参数 签到和断线重连时候初始化 分钟学时发送之前更新字典 并存本地 短线重连用来恢复计时
  39. NSTimer *timer;//学时计时器(它的存在可以且已经有用于判断是否在计时中)
  40. NSInteger seconds;//学时计数
  41. MNQDev *mnqDev;//模拟器
  42. //过程验证
  43. BOOL onProcessVerification;//是否正在过程验证
  44. NSInteger processVerificationTime;//过程验证时间 == 0 未开启, >0 开启需要验证
  45. NSInteger processVerificationMaxCount;//允许失败次数 自己yy了一下==0不限制,服务器应该不会返回0吧 目前设置的是3次 超过三次就app发起签退
  46. NSInteger processVerificationFailureCount;//失败次数
  47. NSInteger photoSeconds;//拍照时间
  48. //学时相关
  49. NSString *subject; //当前训练科目
  50. NSString *classId; //课堂ID
  51. NSString *classCode; //课程编码
  52. NSString *trainNum; //学时编号
  53. NSString *trainOrderNum;//学时编号用序列 每天置0
  54. NSArray *minuteTrainArray;//分钟学时数组 用于汇总学时时候 获取本地库未上传的分钟学时
  55. NSMutableArray<NSString *> *dataSourceArray; //表的数据源 组成部分:颜色数字+提示字 1为红色失败 2为绿色成功
  56. //地图定位
  57. CLLocationCoordinate2D myCoordinate;//定位我的位置
  58. CLLocationManager *locationManager;//系统gps
  59. //蓝牙相关
  60. NSTimer *signInTimer;//发起拍照命令等待计时器
  61. NSString *peripheralName;//蓝牙名称
  62. BOOL isConnectBlueTooth;//是否与蓝牙连接状态
  63. NSInteger disconnectSeconds;//蓝牙断开的时长
  64. //蓝牙每一包
  65. NSString *blueToothOrder;//最新的蓝牙命令 用于蓝牙未回应重新作操作
  66. BOOL dataIsLack;//如果为yes代表数据不完整 需要下次接收到的数据拼接
  67. NSInteger lackVertifyTime;//蓝牙数据分次返回等待时间,超过5s重新发送命令
  68. NSString *lastData;//接收蓝牙数据时 一个数据包分次返回时 之前的数据
  69. //组合拳限制命令重复请求 不过只有照片的时候用到了。计时器1拍照启动 计时器2开始计时启动(以后不再有重要蓝牙命令)
  70. NSString *blueToothOrder_old;//记录旧的蓝牙命令,为reOrderCount服务
  71. NSInteger reOrderCount;//蓝牙重新请求的次数 如果大于三次 不继续请求 接收到回应就重置 防治无限请求蓝牙
  72. //图片相关
  73. NSString *imageData;//图片数据
  74. NSInteger imgLongth;//图片的总长度 或者每个包的长度
  75. //升级包data
  76. NSString *nowVersion;
  77. NSString *packgeHexStr;//模拟设备升级包
  78. //UI
  79. UIScrollView *mainScrView;
  80. NSMutableArray *detailLabArr; //学时信息表格lab
  81. UILabel *timeLabel; //计时lable
  82. NSString *logs; //训练日志
  83. NSArray *stepTitlesArr; //步骤内容数组
  84. //保留(未使用)
  85. UIView *trainView; //计时状态学时展示部分相关view
  86. UITableView *mainTableView; //trainview addsubview maintableview
  87. MBProgressHUD *openBlueToothMbp;
  88. }
  89. //蓝牙
  90. @property (nonatomic, strong) CBCentralManager *cbcManager;
  91. /** 已连接的外设*/
  92. @property (nonatomic, strong) CBPeripheral *peripheral;
  93. /** 要写入的特征值*/
  94. @property (nonatomic, strong) CBCharacteristic *writeCharacteristic;
  95. /** 要读的特征值*/
  96. @property (nonatomic, strong) CBCharacteristic *readCharacteristic;
  97. //当前步骤
  98. @property(nonatomic,assign)StepImageView *currentStep;
  99. @property(nonatomic,strong)NSArray *stepsArr;
  100. @property(nonatomic,strong)NSArray *arrowArr;
  101. @property (nonatomic, strong) UIImageView *photoImg;
  102. //UI
  103. @property (strong, nonatomic) UILabel *statusLab;
  104. @property (strong, nonatomic) UIView *trainMessageView;
  105. @property (strong, nonatomic) StepImageView *step1;//设备连接
  106. @property (strong, nonatomic) StepImageView *step2;//模拟设备升级
  107. @property (strong, nonatomic) StepImageView *step3;//身份/设备信息验证
  108. @property (strong, nonatomic) StepImageView *step4;//人脸比对
  109. @property (strong, nonatomic) StepImageView *step5;//训练计时中
  110. @property (strong, nonatomic) StepImageView *step6;//签退
  111. @end
  112. @implementation MNQtTrainVC
  113. - (void)viewDidLoad {
  114. [super viewDidLoad];
  115. self.title = @"模拟计时";
  116. self.view.backgroundColor = backGroundColor;
  117. dateFormatter = [[NSDateFormatter alloc] init];
  118. dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
  119. isConnectBlueTooth = NO;
  120. dataSourceArray = [NSMutableArray array];
  121. logs = @"";
  122. seconds = 0;
  123. onProcessVerification = NO;
  124. processVerificationTime = [[NSString stringWithFormat:@"%@",RQ_USER_MANAGER.currentUser.mnqFacePorcess] integerValue];
  125. processVerificationMaxCount = [[NSString stringWithFormat:@"%@",RQ_USER_MANAGER.currentUser.mnqFaceCount] integerValue];
  126. //测试用
  127. // processVerificationTime = 2;
  128. // processVerificationMaxCount = 2;
  129. //初始化gatherTrainDic(这一步可以拿到状态接口返回并存储的claaid等课时相关信息)
  130. [self getGatherTrainDic];
  131. //UI
  132. [self myInit];
  133. [self customTrainMessageView];//创建学时记录“表”
  134. [self reloadDetailLab];//教学信息更新(这个其实可以考虑放在goonTrain和beginTime)
  135. //开启定位(设置允许后台定位)
  136. [self setGPSLocationService];
  137. //根据mn状态,下一步
  138. [self nextWithCurrentStatu:[myDelegate.mnTrainType integerValue]];
  139. }
  140. -(void)viewDidAppear:(BOOL)animated{
  141. [super viewDidAppear:animated];
  142. }
  143. -(void)viewDidDisappear:(BOOL)animated{
  144. [super viewDidDisappear:animated];
  145. if (signInTimer.isValid) {
  146. [signInTimer invalidate];
  147. signInTimer = nil;
  148. }
  149. if (timer == nil) {
  150. [locationManager stopUpdatingLocation];
  151. [self closeBlueTooth];
  152. }
  153. }
  154. - (void)didReceiveMemoryWarning {
  155. [super didReceiveMemoryWarning];
  156. // Dispose of any resources that can be recreated.
  157. }
  158. #pragma mark - 设置步骤
  159. //1-6
  160. - (void)setCurrentStep:(StepImageView *)currentStep
  161. {
  162. if (_currentStep == currentStep) {
  163. return;
  164. }
  165. _currentStep = currentStep;
  166. NSInteger index = [_stepsArr indexOfObject:currentStep];
  167. //箭头
  168. if (currentStep != _step6) {
  169. UIButton *currentArrow = _arrowArr[index];
  170. for (UIButton *arrow in _arrowArr) {
  171. if (arrow == currentArrow) {
  172. arrow.selected = YES;
  173. }else{
  174. arrow.selected = NO;
  175. }
  176. }
  177. }
  178. //step
  179. //训练中蓝牙断开
  180. if (_currentStep == _step1 && timer) {
  181. _step1.status = ClickStatusMid;
  182. _step2.status = ClickStatusHaveDone;
  183. _step3.status = ClickStatusHaveDone;
  184. _step4.status = ClickStatusHaveDone;
  185. _step5.status = ClickStatuseNormal;
  186. _step6.status = ClickStatusHighlighted;
  187. return;
  188. }
  189. for (StepImageView *step in _stepsArr) {
  190. if (step == _currentStep) {
  191. step.status = ClickStatusMid;
  192. if (step == _step5) {
  193. _step6.status = ClickStatusHighlighted;
  194. }
  195. break;
  196. }else{
  197. step.status = ClickStatusHaveDone;
  198. }
  199. }
  200. }
  201. #pragma mark -添加日志记录
  202. -(void)addLogsWithConten:(NSString *)content status:(NSInteger)status{
  203. NSString *statusStr = @"";
  204. switch (status) {
  205. case 0:
  206. statusStr = @"失败";
  207. break;
  208. case 1:
  209. statusStr = @"中...";
  210. break;
  211. case 2:
  212. statusStr = @"成功";
  213. break;
  214. default:
  215. break;
  216. }
  217. [dateFormatter setDateFormat:@"HH:mm:ss "];
  218. NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
  219. // NSString *logStr = [NSString stringWithFormat:@"%@: %@%@\n",dateString,content,statusStr];
  220. // logs = [logs stringByAppendingString:logStr];
  221. //这里和安卓的不一样 iOS日志统计的状态栏显示的东西
  222. if (status == 4396) {
  223. logs = [logs stringByAppendingString:[NSString stringWithFormat:@"%@: %@\n",dateString,content]];
  224. }
  225. return;
  226. }
  227. #pragma mark -UI
  228. -(void)myInit{
  229. [self customNavigationBar];
  230. UIScrollView *sv = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, kSize.width, kSize.height-kNavOffSet-kSafeAreaBottomHeight)];
  231. mainScrView = sv;
  232. sv.backgroundColor = backGroundColor;
  233. [self.view addSubview:sv];
  234. CGFloat x,y,w,h;
  235. x = y = 0;
  236. w = kSize.width;
  237. h = 56;
  238. UIView *view = [[UIView alloc]setxywh];
  239. view.backgroundColor = backGroundColor;
  240. [sv addSubview:view];
  241. x = 5;
  242. h = 36;
  243. y = 10;
  244. w = 40;
  245. UILabel *lab = [[UILabel alloc]setxywh];
  246. [lab setText:@"状态:" Font:Font17 TextColor:kTitleColor];
  247. [view addSubview:lab];
  248. x += w;
  249. w = kSize.width - x - 90;
  250. lab = [[UILabel alloc]setxywh];
  251. _statusLab = lab;
  252. [lab setText:@"" Font:Font17 TextColor:subTitleColor];
  253. [view addSubview:lab];
  254. x = kSize.width - 75;
  255. w = 70;
  256. UIButton *checkMinRecordBtn = [UIButton buttonWithType:UIButtonTypeSystem];
  257. checkMinRecordBtn.layer.masksToBounds = YES;
  258. checkMinRecordBtn.layer.cornerRadius = 5;
  259. checkMinRecordBtn.frame = CGRectMake(x, y, w, h);
  260. [checkMinRecordBtn setTitle:@"训练日志" textColor:[UIColor whiteColor] Font:16 fotState:UIControlStateNormal];
  261. [checkMinRecordBtn addTarget:self action:@selector(checkLogs:) forControlEvents:UIControlEventTouchUpInside];
  262. checkMinRecordBtn.backgroundColor = RQ_MAIN_COLOR;
  263. [view addSubview:checkMinRecordBtn];
  264. x = 5;
  265. y = CGRectGetMaxY(view.frame) + 10;
  266. w = kSize.width - 10;
  267. h = w * 70/300;
  268. view = [[UIView alloc]setxywh];
  269. _trainMessageView = view;
  270. view.backgroundColor = backGroundColor;
  271. [sv addSubview:view];
  272. [self customTrainMessageView];
  273. //
  274. x = 5;
  275. y = CGRectGetMaxY(view.frame) + 10;
  276. w = kSize.width-10;
  277. h = kSize.width+50;
  278. view = [[UIView alloc]setxywh];
  279. view.backgroundColor = backGroundColor;
  280. [sv addSubview:view];
  281. [self customStepsWithView:(UIView *)view];
  282. sv.contentSize = CGSizeMake(0, y+h);
  283. }
  284. -(void)customTrainMessageView
  285. {
  286. CGFloat w = (_trainMessageView.width)/4;
  287. CGFloat h = _trainMessageView.height*0.4;
  288. CGFloat lineW = 1;
  289. NSArray *titles = @[@"所需学时",@"已完成学时",@"剩余学时",@"当天学时"];
  290. detailLabArr = [NSMutableArray new];
  291. for (int i=0; i<2; i++) {
  292. for (int j=0; j<4; j++) {
  293. UILabel *lab = [[UILabel alloc]initWithFrame:CGRectMake(j%4*w, h/4+i*h, w, h)];
  294. lab.adjustsFontSizeToFitWidth = YES;
  295. lab.textAlignment = NSTextAlignmentCenter;
  296. [self.trainMessageView addSubview:lab];
  297. if (i==0) {
  298. lab.text = titles[j];
  299. lab.textColor = subTitleColor;
  300. }else{
  301. lab.textColor = kTitleColor;
  302. if (j==3) {
  303. timeLabel = lab;
  304. }else{
  305. [detailLabArr addObject:lab];
  306. }
  307. }
  308. //line
  309. //垂直
  310. if (j != 0) {
  311. UIView *lineVer = [[UIView alloc]initWithFrame:CGRectMake(w*j, h/2, lineW, h*1.5)];
  312. lineVer.backgroundColor = RQlineColor;
  313. [self.trainMessageView addSubview:lineVer];
  314. }
  315. }
  316. }
  317. }
  318. - (IBAction)checkLogs:(id)sender {
  319. [PrecautionsView showPrecautionsViewWithContent:logs];
  320. }
  321. -(void)checkPrecautions
  322. {
  323. [PrecautionsView showPrecautionsViewWithContent:@"注意:\n\n1.训练过程中,请保持蓝牙连接,蓝牙断开计时会停止。\n\n2.学员中途短暂离开无须签退;当学员再次回到模拟设备附近计时会自动恢复。若没有收到计时恢复语音提示,请前往“模拟计时”确认计时是否恢复。\n\n3.允许app切换到后台运行,允许手机黑屏,不允许关闭app。(需要设置\"始终允许访问位置\",如果选择仅在\"使用应用期间\"访问位置,则不能在后台计时。查看方式:系统设置-优易学车-位置)\n\n4.部分手机或因内存不足,在app后台运行的时候被手机系统关闭,计时停止。(如果遇到这种情况,请学员关闭其它部分app,保持“优易学车”在前台运行)\n\n5.部分地区根据规定需要进行过程验证的,请在收到过程验证通知后(app停止计时),前往模拟计时点击人脸验证,通过后将恢复计时 \n\n6.图标说明:\n浅灰色--未执行,不可操作;\n深灰色--已执行,不可操作;\n正蓝色--执行中,不可操作;\n正绿色--执行失败或未执行,可操作;\n(注:仅绿色表示可以被选中执行该步骤,其它颜色不能被选中)\n\n祝您体验愉快"];
  324. }
  325. -(void)customStepsWithView:(UIView *)view{
  326. NSArray *imgs = @[@"2_mn",@"1_mn",@"3_mn",@"4_mn",@"5_mn",@"6_mn"];
  327. NSArray *titles = @[@"设备连接",@"终端升级",@"身份认证",@"人脸验证",@"训练过程",@"学员签退"];
  328. stepTitlesArr = titles;
  329. NSArray *arrowImgs = @[@"mn_right",@"mn_down",@"mn_left",@"mn_down",@"mn_right"];
  330. CGFloat wid = kSize.width/3;
  331. CGFloat bd = kSize.width/3/3;
  332. NSMutableArray *tmpArr = [NSMutableArray new];
  333. NSMutableArray *jiantouArr = [NSMutableArray new];
  334. for (int i=0; i<6; i++) {
  335. CGRect frame = CGRectMake(bd+(wid+bd)*(i%2), 0+(wid+0)*(i/2), wid, wid);
  336. if (i==2 || i==3) {
  337. frame.origin.x = bd+(wid+bd)*((i+1)%2);
  338. }
  339. //步骤
  340. StepImageView *step = [[StepImageView alloc]initWithFrame:frame image:imgs[i] title:titles[i]];
  341. [view addSubview:step];
  342. [tmpArr addObject:step];
  343. //箭头
  344. if (i==5) {
  345. continue;
  346. }
  347. UIButton *jiantou = [UIButton buttonWithType:UIButtonTypeCustom];
  348. jiantou.userInteractionEnabled = NO;
  349. jiantou.frame = CGRectMake(0, 0, 20, 20);
  350. if (i==1 || i==3) {
  351. jiantou.center = CGPointMake(step.center.x, CGRectGetMaxY(step.frame)+wid/8);
  352. }else{
  353. jiantou.center = CGPointMake(kSize.width/2, step.y+step.height/2);
  354. }
  355. UIImage *normalImg = [[UIImage imageNamed:arrowImgs[i]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
  356. [jiantou setImage:normalImg forState:UIControlStateNormal];
  357. UIImage *sleImg = [[UIImage imageNamed:[NSString stringWithFormat:@"%@_H",arrowImgs[i]]] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
  358. [jiantou setImage:sleImg forState:UIControlStateSelected];
  359. [view addSubview:jiantou];
  360. [jiantouArr addObject:jiantou];
  361. }
  362. _arrowArr = [NSArray arrayWithArray:jiantouArr];
  363. _stepsArr = [NSArray arrayWithArray:tmpArr];
  364. _step1 = _stepsArr[0];
  365. _step2 = _stepsArr[1];
  366. _step3 = _stepsArr[2];
  367. _step4 = _stepsArr[3];
  368. _step5 = _stepsArr[4];
  369. _step6 = _stepsArr[5];
  370. __weak typeof(self)weakSelf = self;
  371. _step1.clickBlock = ^{
  372. NSLog(@"clickStep1");
  373. weakSelf.step1.status = ClickStatusHighlighted;
  374. if([myDelegate.mnTrainType isEqualToString:@"2"]){
  375. [weakSelf getBlueToothAndDevOrder];
  376. }else if ([myDelegate.mnTrainType isEqualToString:@"0"]) {//正在计时
  377. if (weakSelf.cbcManager && weakSelf.peripheral) {
  378. [weakSelf.cbcManager connectPeripheral:weakSelf.peripheral options:nil];
  379. }
  380. }
  381. };
  382. _step2.clickBlock = ^{
  383. NSLog(@"clickStep2");
  384. [weakSelf UpdateMNDev];
  385. };
  386. _step3.clickBlock = ^{
  387. NSLog(@"clickStep3");
  388. blueToothOrder = kGetDevNumAndStuCardOrder;
  389. [weakSelf sendBlueToothOrder];
  390. };
  391. _step4.clickBlock = ^{
  392. NSLog(@"clickStep4");
  393. [weakSelf takePhotoByBlueTooth];
  394. };
  395. _step5.clickBlock = ^{
  396. NSLog(@"clickStep5");
  397. };
  398. _step6.clickBlock = ^{
  399. NSLog(@"clickStep6");
  400. [weakSelf wantSignOut];
  401. };
  402. }
  403. -(void)customNavigationBar{
  404. //返回
  405. UIBarButtonItem* backBbi = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"backIcon.png"] style:UIBarButtonItemStylePlain target:self action:@selector(goBackByNavigation)];
  406. [self.navigationItem setLeftBarButtonItem:backBbi];
  407. self.navigationController.navigationBar.translucent = NO;
  408. //注意事项
  409. backBbi = [[UIBarButtonItem alloc] initWithTitle:@"注意事项" style:UIBarButtonItemStylePlain target:self action:@selector(checkPrecautions)];
  410. [self.navigationItem setRightBarButtonItem:backBbi];
  411. self.navigationController.navigationBar.translucent = NO;
  412. }
  413. -(void)goBackByNavigation{
  414. if (self.navigationController.presentingViewController) {
  415. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  416. }else{
  417. [self.navigationController popViewControllerAnimated:YES];
  418. }
  419. }
  420. #pragma mark - UI更新
  421. -(void)reloadDetailLab{
  422. //更新UI展示内容
  423. if (gatherTrainDic[@"subject"]) {
  424. subject = gatherTrainDic[@"subject"];
  425. self.navigationItem.title = [NSString stringWithFormat:@"科目%@模拟计时",[subject isEqualToString:@"2"]?@"二":@"三"];
  426. }
  427. seconds = [gatherTrainDic[@"allTime"] integerValue];
  428. timeLabel.text = @"00:00:00";
  429. if (seconds != 0) {
  430. [self upDateTime];
  431. }
  432. int i=0;
  433. for (UILabel * detailLab in detailLabArr) {
  434. switch (i) {
  435. case 0:
  436. detailLab.text = [self getTimeByMinString:gatherTrainDic[@"allNeed"]];
  437. break;
  438. case 1:
  439. detailLab.text = [self getTimeByMinString:gatherTrainDic[@"allCompelet"]];
  440. break;
  441. case 2:
  442. {
  443. NSInteger allComplete = [gatherTrainDic[@"allCompelet"] integerValue];
  444. NSInteger allNeed = [gatherTrainDic[@"allNeed"] integerValue];
  445. if (allNeed - allComplete > 0) {
  446. detailLab.text = [self getTimeByMinString:[NSString stringWithFormat:@"%ld",allNeed - allComplete]];
  447. }else{
  448. detailLab.text = @"0分";
  449. }
  450. }
  451. break;
  452. default:
  453. break;
  454. }
  455. i++;
  456. }
  457. }
  458. -(NSString *)getTimeByMinString:(NSString *)minStr{
  459. NSInteger min = [minStr integerValue];
  460. NSInteger h = min/60;
  461. NSInteger m = min%60;
  462. if (h != 0) {
  463. return [NSString stringWithFormat:@"%ld小时%ld分",h,m];
  464. }
  465. return [NSString stringWithFormat:@"%ld分",m];
  466. }
  467. //更新UI上的时间
  468. -(void)upDateTime{
  469. int hour = (int)seconds / 3600;
  470. int min = (int)seconds % 3600 / 60;
  471. int second = (int)seconds % 3600 % 60;
  472. NSString *min_s;
  473. NSString *second_s;
  474. if (min<10) {
  475. min_s=[NSString stringWithFormat:@"0%d",min];
  476. }else{
  477. min_s=[NSString stringWithFormat:@"%d",min];
  478. }
  479. if (second<10) {
  480. second_s=[NSString stringWithFormat:@"0%d",second];
  481. }else{
  482. second_s=[NSString stringWithFormat:@"%d",second];
  483. }
  484. timeLabel.text = [NSString stringWithFormat:@"0%d:%@:%@",hour,min_s,second_s];
  485. }
  486. /**
  487. 本方法会播放语音/本地通知提示,更新状态栏,新增日志记录
  488. @param string 提示/日志/状态内容
  489. @param audioStr 如果为nil则处于后台不添加本地通知
  490. */
  491. - (void)addBlueToothLabelWithContent:(NSString *)string mp3:(NSString *)audioStr{
  492. [self addLogsWithConten:string status:4396];
  493. _statusLab.text = string;
  494. if(audioStr){
  495. if (myDelegate.isBackgroundTask) {
  496. [self addLocalNotificationWithAudio:audioStr];
  497. }else {
  498. string = [string stringByReplacingOccurrencesOfString:@"重" withString:@"虫"];
  499. [Tools playAudioWithString:string];
  500. }
  501. }
  502. }
  503. #pragma mark - start
  504. - (void)nextWithCurrentStatu:(NSInteger )statu{
  505. self.currentStep = _step1;
  506. /*
  507. Type 定义 0 成功获取学员信息
  508. 1 学员还未在模拟器签到(这种状态是不允许进入当前VC的)
  509. 2.已在模拟设备登录 未进行人脸比对
  510. 3 无法获取到当前训练科目 (先选科目以后,不存在该状态。科目会在签到(人脸验证)的时候提交)
  511. 4 隔日签到记录
  512. 5 分钟学时科目异常
  513. */
  514. //已登录但未进行人脸比对签到
  515. if (statu == 2) {
  516. NSString *kemu = gatherTrainDic[@"subject"];
  517. if (kemu && kemu.length != 0) {
  518. //获取蓝牙名/序列号
  519. [self getBlueToothAndDevOrder];
  520. }else{
  521. [self slelectTrainSubject];
  522. return;
  523. }
  524. return;
  525. }
  526. //成功获取学员信息
  527. if (statu == 0) {
  528. classId = gatherTrainDic[@"classId"];
  529. classCode = gatherTrainDic[@"classCode"];
  530. //训练中,恢复计时
  531. [self goonTrain];
  532. return;
  533. }
  534. //隔日训练
  535. if (statu == 4) {
  536. _step6.status = ClickStatusMid;
  537. //查看是否存在本地学时
  538. minuteTrainArray = [DB_Helper quearyTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  539. if (minuteTrainArray.count > 0) {
  540. [self uploadAppTrainPlWithSignOut:YES];
  541. }else{
  542. [gatherTrainDic setValue:@"2" forKey:@"isOver"];
  543. classId = gatherTrainDic[@"classId"];
  544. classCode = gatherTrainDic[@"classCode"];
  545. minuteTrainArray = [DB_Helper quearyTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  546. if (minuteTrainArray.count > 0) {
  547. [self uploadAppTrainPlWithSignOut:YES];
  548. }else{
  549. [self signOut];
  550. }
  551. }
  552. return;
  553. }
  554. //以下两种状态(1,3)此处无需判断
  555. //学员还未在模拟器签到(这种状态是不允许进入当前VC的,可以无视)
  556. // if(statu == 1){
  557. //
  558. // UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:@"提醒" message:@"当前暂未检测到PC端已登录信息;如需计时,请扫描PC端登录二维码完成登录" preferredStyle:UIAlertControllerStyleAlert];
  559. //
  560. // [alertFind addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  561. // [self goBackByNavigation];
  562. // }]];
  563. // [alertFind addAction:[UIAlertAction actionWithTitle:@"扫码登录" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  564. //
  565. // [self openScan];
  566. // }]];
  567. // [alertFind addAction:[UIAlertAction actionWithTitle:@"PC端已登录" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  568. //
  569. // [self getCurrentState];打开的时候考虑一下获取状态后的操作
  570. // }]];
  571. //
  572. // [self presentViewController:alertFind animated:true completion:nil];
  573. //
  574. // return;
  575. // }
  576. if (statu == 5) {
  577. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"分钟学时科目异常" preferredStyle:UIAlertControllerStyleAlert];
  578. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  579. [self goBackByNavigation];
  580. }]];
  581. [self presentViewController:alertFind animated:true completion:nil];
  582. return;
  583. }
  584. if (statu == 3) {
  585. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"无法获取到当前训练科目" preferredStyle:UIAlertControllerStyleAlert];
  586. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  587. [self goBackByNavigation];
  588. }]];
  589. [self presentViewController:alertFind animated:true completion:nil];
  590. return;
  591. }
  592. }
  593. #pragma mark 蓝牙相关
  594. - (void)searchBlueTooth {
  595. dataIsLack = NO;
  596. lastData = @"";
  597. blueToothOrder = @"";
  598. reOrderCount = 0;
  599. if ([myDelegate.mnTrainType isEqualToString: @"0"]) {//说明是断线重连
  600. minuteTrainDic = [NSMutableDictionary dictionary];
  601. [minuteTrainDic setValue:gatherTrainDic[@"classId"] forKey:@"classId"];
  602. [minuteTrainDic setValue:gatherTrainDic[@"classCode"] forKey:@"classCode"];
  603. [minuteTrainDic setValue:gatherTrainDic[@"trainNum"] forKey:@"trainNum"];
  604. trainNum = gatherTrainDic[@"trainNum"];
  605. photoSeconds = [gatherTrainDic[@"photoTime"] integerValue];
  606. timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
  607. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];//处理UI交互阻塞计时
  608. myDelegate.mnPeriodVC = self;
  609. // [self addBlueToothLabelWithContent:@"断线重连成功,已恢复训练" mp3:nil];
  610. // [Tools playAudioWithString:@"断线虫连成功,已恢复训练"];
  611. disconnectSeconds = 4;//替换上面的提醒zzz
  612. }
  613. if (!_cbcManager) {
  614. _cbcManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
  615. }
  616. }
  617. //只要中心管理者初始化 就会触发此代理方法 判断手机蓝牙状态
  618. - (void)centralManagerDidUpdateState:(CBCentralManager *)central
  619. {
  620. switch (central.state) {
  621. case 0:
  622. //状态未知
  623. //NSLog(@"CBCentralManagerStateUnknown");
  624. break;
  625. case 1:
  626. //连接断开 即将重置
  627. //NSLog(@"CBCentralManagerStateResetting");
  628. break;
  629. case 2:
  630. //该平台不支持蓝牙
  631. //NSLog(@"CBCentralManagerStateUnsupported");
  632. break;
  633. case 3:
  634. //未授权蓝牙使用 hovertree.com
  635. //NSLog(@"CBCentralManagerStateUnauthorized");
  636. {
  637. [RQ_SHARE_FUNCTION showAlertWithTitle:@"温馨提示" message:@"请在“设置”中打开蓝牙访问权限" alertControllerStyle:UIAlertControllerStyleAlert cancelButtonTitle:@"确定" otherButtonTitles:nil otherButtonStyles:nil showInWindow:NO completion:nil];
  638. }
  639. break;
  640. case 4:
  641. {
  642. //蓝牙未开启
  643. [self addBlueToothLabelWithContent:@"蓝牙未开启" mp3:nil];
  644. //NSLog(@"CBCentralManagerStatePoweredOff");
  645. if (peripheralName.length > 0) {
  646. // showMsgByAlert(self, @"请打开手机蓝牙");
  647. openBlueToothMbp = [MBProgressHUD showMessage:@"请打开手机蓝牙" ToView:self.view];//这个加载每一次都是新建的 不用担心和其它公用
  648. [Tools playAudioWithString:@"请打开手机蓝牙"];
  649. }
  650. self.currentStep = _step1;
  651. isConnectBlueTooth = NO;
  652. }
  653. break;
  654. case 5:
  655. {
  656. //蓝牙已开启
  657. if (openBlueToothMbp) {
  658. [openBlueToothMbp hideAnimated:YES];
  659. }
  660. [self addBlueToothLabelWithContent:@"蓝牙已开启" mp3:nil];
  661. //NSLog(@"CBCentralManagerStatePoweredOn");
  662. if (peripheralName.length < 1) {
  663. return;
  664. }
  665. // 搜索外设
  666. if (self.peripheral) {
  667. return;
  668. }
  669. // 搜索成功之后,会调用找到外设的代理方法
  670. [self addBlueToothLabelWithContent:@"正在连接模拟设备..." mp3:nil];
  671. [self.cbcManager scanForPeripheralsWithServices:nil // 通过某些服务筛选外设
  672. options:nil]; // dict,条件
  673. }
  674. break;
  675. default:
  676. break;
  677. }
  678. }
  679. // 发现外设后调用的方法
  680. - (void)centralManager:(CBCentralManager *)central // 中心管理者
  681. didDiscoverPeripheral:(CBPeripheral *)peripheral // 外设
  682. advertisementData:(NSDictionary *)advertisementData // 外设携带的数据
  683. RSSI:(NSNumber *)RSSI // 外设发出的蓝牙信号强度
  684. {
  685. NSLog(@"搜索到的外设----><>%@----><>%@",peripheral.name,peripheralName);
  686. //如果蓝牙名称和二维码中的一致 就可以连接了 最好能关闭蓝牙的扫描
  687. if ([peripheral.name containsString:peripheralName]) {
  688. self.peripheral = peripheral;
  689. [self.cbcManager stopScan];
  690. [_cbcManager connectPeripheral:self.peripheral options:nil];
  691. }
  692. }
  693. // 中心管理者连接外设成功
  694. - (void)centralManager:(CBCentralManager *)central // 中心管理者
  695. didConnectPeripheral:(CBPeripheral *)peripheral // 外设
  696. {
  697. NSLog(@"%s, line = %d, %@=连接成功", __FUNCTION__, __LINE__, peripheral.name);
  698. isConnectBlueTooth = YES;
  699. // 连接成功之后,可以进行服务和特征的发现
  700. // 设置外设的代理
  701. self.peripheral.delegate = self;
  702. // 外设发现服务,传nil代表不过滤
  703. [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
  704. if (!timer) {//非计时
  705. [self addBlueToothLabelWithContent:@"模拟设备连接成功" mp3:nil];//如果学员在150s内没有开始计时启动timer 那么训练日志里面就会多出一条这个记录
  706. }else{//计时
  707. //计时离线超过3s语音提示/恢复按钮UI步骤。timer存在且大于3 计时中
  708. if (disconnectSeconds > 3){
  709. //判断是否需要执行过程验证,恢复到不同的操作
  710. if (onProcessVerification) {
  711. [self addBlueToothLabelWithContent:@"模拟设备连接成功" mp3:nil];
  712. _currentStep = _step4;//不触发setCurrentStep 步骤的方法
  713. _step4.status = ClickStatusHighlighted;//这里不考虑学员自己在照片传输到一半的过程中,离开蓝牙范围。统一激活人脸签到按钮,点击可以重新发拍照指令。
  714. _step1.status = ClickStatusHaveDone;
  715. _step5.status = ClickStatusHaveDone;
  716. }else{
  717. self.currentStep = _step5;
  718. //这里恢复计时的时候判断一下是否需要过程验证
  719. if (photoSeconds%(processVerificationTime*60) != 0) {
  720. [self addBlueToothLabelWithContent:@"设备连接成功,恢复计时" mp3:@"mn_blueToothConnect.mp3"];
  721. }
  722. }
  723. }
  724. }
  725. disconnectSeconds = 0;
  726. }
  727. // 外设连接失败
  728. - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  729. {
  730. isConnectBlueTooth = NO;
  731. NSLog(@"%s, line = %d, %@=连接失败", __FUNCTION__, __LINE__, peripheral.name);
  732. [self addBlueToothLabelWithContent:@"模拟设备连接失败!" mp3:nil];
  733. if (self.peripheral) {
  734. [_cbcManager connectPeripheral:self.peripheral options:nil];
  735. }
  736. }
  737. // 丢失连接
  738. - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
  739. {
  740. NSLog(@"%s, line = %d, %@=断开连接", __FUNCTION__, __LINE__, peripheral.name);
  741. isConnectBlueTooth = NO;
  742. if (self.cbcManager && self.peripheral) {
  743. [self.cbcManager connectPeripheral:self.peripheral options:nil];
  744. }
  745. //因为硬件那边的某些问题 现在设置连接间隔不符合苹果的要求 会固定每155s(测试)断开一次。所以断开提示设置为3s之后才提示
  746. dispatch_time_t zz = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
  747. dispatch_after(zz, dispatch_get_main_queue(), ^{
  748. if (timer && !isConnectBlueTooth) {
  749. self.currentStep = _step1;
  750. _step1.status = ClickStatusHighlighted;
  751. if (timer) {
  752. [self addBlueToothLabelWithContent:@"设备连接断开,计时停止" mp3:@"mn_blueToothDisconnect.mp3"];
  753. }else{
  754. [self addBlueToothLabelWithContent:@"设备连接断开" mp3:nil];
  755. }
  756. }
  757. });
  758. }
  759. //查找服务的所有特征
  760. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
  761. if (error)
  762. {
  763. NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
  764. [self addBlueToothLabelWithContent:@"蓝牙服务搜索失败" mp3:nil];
  765. return;
  766. }
  767. //NSLog(@"services---><>%@",peripheral.services);
  768. //服务并不是我们的目标,也没有实际意义。我们需要用的是服务下的特征,查询(每一个服务下的若干)特征
  769. for (CBService *service in peripheral.services)
  770. {
  771. NSLog(@"Service found with UUID: %@", service.UUID);
  772. // Discovers the characteristics for a given service
  773. if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
  774. //搜索特性
  775. [self.peripheral discoverCharacteristics:nil forService:service];
  776. }
  777. }
  778. }
  779. // 发现外设服务里的特征的时候调用的代理方法(这个是比较重要的方法,你在这里可以通过事先知道UUID找到你需要的特征,订阅特征,或者这里写入数据给特征也可以)
  780. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
  781. {
  782. if (error) {
  783. NSLog(@"搜索特性失败 characteristic: %@", [error localizedDescription]);
  784. [self addBlueToothLabelWithContent:@"蓝牙服务特征搜索失败" mp3:nil];
  785. return;
  786. }
  787. for (CBCharacteristic *cha in service.characteristics) {
  788. if ([cha.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID_reade]]) {
  789. // NSLog(@"读取特征-->>>%@",cha);
  790. self.readCharacteristic = cha;
  791. [peripheral setNotifyValue:YES forCharacteristic:cha];
  792. /*
  793. 最后还有一个重要的补充,当我们已经找到了我们感兴趣的characteristic后,他的value并不一定通过1方法或者2方法就可以接收的,就算可以接收,有时候只能用1,有时候只能用2。
  794. 这牵扯到characteristic的一个property
  795. 当为read(0x02)的时候,我们用1方法可以查看,用2就会出错。当为notify(0x10)的时候我们就得用2方法。其他就不说明了,比如write就只能写value······
  796. typedef enum {
  797. CBCharacteristicPropertyBroadcast = 0x01,
  798. CBCharacteristicPropertyRead = 0x02,
  799. CBCharacteristicPropertyWriteWithoutResponse = 0x04,
  800. CBCharacteristicPropertyWrite = 0x08,
  801. CBCharacteristicPropertyNotify = 0x10,
  802. CBCharacteristicPropertyIndicate = 0x20,
  803. CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
  804. CBCharacteristicPropertyExtendedProperties = 0x80,
  805. CBCharacteristicPropertyNotifyEncryptionRequired = 0x100,
  806. CBCharacteristicPropertyIndicateEncryptionRequired = 0x200,
  807. } CBCharacteristicProperties;
  808. 所以当我们想查看value的时候,先了解一下这个characteristic的property,看是不是能让我们读的。怎么查看?找BLE外设的设备厂商或者查看外设的说明书。
  809. 推荐将value经常变化的characteristic的property设为notify
  810. */
  811. }
  812. if ([cha.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID_write]]) {
  813. // NSLog(@"写入特征-->>>%@",cha);//0x04对应的是CBCharacteristicPropertyWriteWithoutResponse || 0x0A对应的是CBCharacteristicPropertyNotify
  814. self.writeCharacteristic = cha;
  815. bool isReal = [myDelegate.mnTrainType integerValue] == 2;//走到这一步只会有 0 成功获取学员信息(学员是计时状态 != app在跑定时器) / 2.已在模拟设备登录,未进行人脸比对
  816. if (isReal && (_currentStep == _step1 || _currentStep == _step2)) {//未开始
  817. //获取版本号
  818. self.currentStep = _step2;
  819. blueToothOrder = kGetMNdevVersion;
  820. [self sendBlueToothOrder];
  821. }
  822. /*非计时状态,中间断开,重发蓝牙指令?
  823. 1.现在每一步都可以点击按钮重新操作,即学员主动发送蓝牙指令;
  824. 2.终端重新连接后会继续以前的动作。app会响应收到的数据执行相关操作
  825. 鉴于以上两点,app就不主动重新发送蓝牙连接了*/
  826. //if(blueToothOrder.length > 0){
  827. // [self sendBlueToothOrder];
  828. //}
  829. }
  830. }
  831. }
  832. - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
  833. if (error) {
  834. NSLog(@"Error changing notification state: %@", error.localizedDescription);
  835. [self addBlueToothLabelWithContent:@"蓝牙服务特征状态异常" mp3:nil];
  836. return;
  837. }
  838. // Exits if it's not the transfer characteristic
  839. if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID_reade]]) {
  840. return;
  841. }
  842. // Notification has started
  843. if (characteristic.isNotifying) {
  844. NSLog(@"Notification began on %@", characteristic);
  845. NSLog(@"%@",characteristic.value);
  846. NSLog(@"%@",[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]);
  847. // [peripheral readValueForCharacteristic:characteristic]; 这里不要再去读啊,否则会收到每一次发送数据的最后一包(20)
  848. } else { // Notification has stopped
  849. NSLog(@"Notification stopped on %@. Disconnecting", characteristic);
  850. }
  851. }
  852. // 更新特征的value的时候会调用 (凡是从蓝牙传过来的数据都要经过这个回调,简单的说这个方法就是你拿数据的唯一方法)
  853. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
  854. {
  855. if ([characteristic isEqual:self.readCharacteristic] ) {
  856. NSString *hexString = [Tools convertDataToHexStr:characteristic.value];
  857. NSLog(@"hexString--->%ld<>%@",seconds,hexString);
  858. lackVertifyTime = 0;
  859. //数据完整性检测
  860. if ([hexString containsString:@"be"] && dataIsLack == NO) {
  861. //数据首包
  862. lastData = hexString;
  863. if (lastData.length < 6) {
  864. return;
  865. }
  866. int dataLength = [Tools getHexNumWithString:[lastData substringWithRange:NSMakeRange(2, 4)]];
  867. if (lastData.length < dataLength*2 + 6) {
  868. dataIsLack = YES;
  869. return;
  870. }
  871. }else {
  872. if (dataIsLack) {//上个包数据不够
  873. hexString = [lastData stringByAppendingString:hexString];
  874. lastData = hexString;
  875. int dataLength = [Tools getHexNumWithString:[lastData substringWithRange:NSMakeRange(2, 4)]];
  876. if (lastData.length < dataLength*2 + 6) {
  877. return;
  878. }else {
  879. dataIsLack = NO;
  880. }
  881. }else{
  882. return;
  883. }
  884. }
  885. if ([hexString containsString:@"be"]) {//防止其它莫名奇妙的指令
  886. NSString *order = [hexString substringWithRange:NSMakeRange(6, 2)];
  887. //获取到模拟设备版本,进行版本判断
  888. if([order isEqualToString:@"0a"]){
  889. [self checkDevVersionWithHexString:hexString];
  890. return;
  891. }
  892. //升级
  893. if ([order isEqualToString:@"0b"]) {
  894. [self sendMsgWithSubPackageWithHexString:hexString];//发送指定升级包
  895. return;
  896. }
  897. //升级成功
  898. if ([order isEqualToString:@"0c"]) {
  899. ShowMsg(@"模拟设备升级完成");
  900. [self addBlueToothLabelWithContent:@"模拟设备升级完成" mp3:nil];
  901. [Tools playAudioWithString:@"模拟设备升级完成"];
  902. return;
  903. }
  904. //获取到终端信息
  905. if ([order isEqualToString:@"03"]) {
  906. [self signFirstCheckWithHexString:hexString];//验证序列号/物理卡id
  907. return;
  908. }
  909. //拍照成功开始返回的照片包(第一包开始)
  910. if ([order isEqualToString:@"17"]) {
  911. [self getDataForPictureWithString:hexString];
  912. return;
  913. }
  914. }
  915. }
  916. }
  917. -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
  918. if (error) {
  919. NSLog(@"写入数据失败-->%@:%@",blueToothOrder,error);
  920. }else{
  921. NSLog(@"写入数据成功-->: %@",blueToothOrder);
  922. }
  923. }
  924. //根据数据内容生成图片
  925. - (void)getDataForPictureWithString:(NSString *)hexString {
  926. //取相片
  927. NSString *len = [hexString substringWithRange:NSMakeRange(2, 4)];
  928. NSInteger packageLength = [Tools getHexNumWithString:len];
  929. if ([blueToothOrder containsString:kGetPhotoData]) {
  930. NSString *newData = [[hexString substringFromIndex:10] stringByReplacingOccurrencesOfString:@"eded" withString:@""];
  931. imageData = [imageData stringByAppendingString:newData];//每次发起拍照命令前 将图片数据清空
  932. NSString *currentHexOrder = [hexString substringWithRange:NSMakeRange(8, 2)];
  933. int pOrderNum = [Tools getHexNumWithString:currentHexOrder];
  934. if ([currentHexOrder isEqualToString:@"00"]) {//循环发送命令
  935. //照片数据全取出 显示照片
  936. NSData *image = [Tools convertHexStrToData:imageData];
  937. //获取到照片 上传服务器
  938. if (image) {
  939. blueToothOrder = @"upLoadWAIT";
  940. //如果是签到拍照生成classId等相关
  941. if (onProcessVerification == NO) {
  942. [self getClassIdAndSoOn];
  943. }
  944. [self upLoadPhotoWithImage:[UIImage imageWithData:image]];
  945. }
  946. }else{
  947. NSString *orderStr = [NSString stringWithFormat:@"%.2x",pOrderNum + 1];////命令+1 然后转为16进制
  948. blueToothOrder = [NSString stringWithFormat:@"%@%@eded",kGetPhotoData,orderStr];
  949. [self sendBlueToothOrder];
  950. }
  951. return;
  952. }
  953. //如果是拍照指令 并且 返回了第一包的数据
  954. if ([blueToothOrder isEqualToString:kTakePhotoOrder] && [[hexString substringWithRange:NSMakeRange(6, 2)] isEqualToString:@"17"]) {
  955. [self addBlueToothLabelWithContent:@"拍照已完成,正在采集照片信息..." mp3:@"mn_takePhotoColloctPic.mp3"];
  956. //拍照成功 发指令获取数据
  957. if (packageLength*2 == hexString.length-6 && [hexString containsString:@"eded"]) {//判断完整性
  958. imageData = [[hexString substringFromIndex:10] stringByReplacingOccurrencesOfString:@"eded" withString:@""];//每次发起拍照命令前 将图片数据清空
  959. blueToothOrder = [NSString stringWithFormat:@"%@02eded",kGetPhotoData];//获取指定图像包
  960. }else{
  961. blueToothOrder = [NSString stringWithFormat:@"%@01eded",kGetPhotoData];//获取指定图像包
  962. }
  963. [self sendBlueToothOrder];
  964. }
  965. }
  966. #pragma mark 一些方法
  967. -(void)checkDevVersionWithHexString:(NSString *)hexString{
  968. NSMutableString *mStr = [NSMutableString stringWithString:hexString];
  969. NSString *str = [[mStr substringFromIndex:6] stringByReplacingOccurrencesOfString:@"eded" withString:@""];
  970. NSArray *arr = [[Tools convertHexStrToString:str] componentsSeparatedByString:@";"];
  971. NSInteger zz = [[NSString stringWithFormat:@"%@",arr[1]] integerValue];
  972. nowVersion = [NSString stringWithFormat:@"%ld",zz];
  973. //版本更新检查
  974. [self UpdateMNDev];//这返回了三个参数,DM91-MN,0003,L000003 我们只用到中间的序列号
  975. }
  976. - (void)signFirstCheckWithHexString:(NSString *)hexString{
  977. NSMutableString *mStr = [NSMutableString stringWithString:hexString];
  978. NSString *str = [[mStr substringFromIndex:6] stringByReplacingOccurrencesOfString:@"eded" withString:@""];
  979. NSArray *arr = [[Tools convertHexStrToString:str] componentsSeparatedByString:@";"];
  980. //如果是返回序列号,判断是否符合服务器返回的序列号,符合则命令拍照
  981. if ([arr[1] isEqualToString: mnqDev.mnqSn]) {
  982. if ([arr[0] containsString:mnqDev.cardId]) {
  983. [self addBlueToothLabelWithContent:@"身份信息认证成功" mp3:nil];
  984. self.currentStep = _step4;
  985. //发起拍照命令
  986. [self takePhotoByBlueTooth];
  987. }else{
  988. _currentStep.status = ClickStatusHighlighted;
  989. showMsgByAlertWithSureBlock(self, @"请刷本人身份证", ^{
  990. _currentStep.status = ClickStatusMid;
  991. blueToothOrder = kGetDevNumAndStuCardOrder;
  992. [self sendBlueToothOrder];
  993. });
  994. [Tools playAudioWithString:@"请刷本人身份证"];
  995. [self addBlueToothLabelWithContent:@"身份信息认证失败" mp3:nil];
  996. }
  997. }else{
  998. _currentStep.status = ClickStatusHighlighted;
  999. showMsgByAlert(self, @"模拟设备序列号不匹配");
  1000. [Tools playAudioWithString:@"模拟设备序列号不匹配"];
  1001. [self addBlueToothLabelWithContent:@"身份信息认证失败" mp3:nil];
  1002. }
  1003. }
  1004. - (void)showAlertTakePhotoFailWithStr:(NSString *)failureStr {
  1005. _currentStep.status = ClickStatusHighlighted;
  1006. blueToothOrder = @"";
  1007. UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:[NSString stringWithFormat:@"人脸比对失败(%@),是否重新采集照片",failureStr] preferredStyle:UIAlertControllerStyleAlert];
  1008. [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  1009. //关闭定时器
  1010. if (signInTimer) {
  1011. [signInTimer invalidate];
  1012. signInTimer = nil;
  1013. }
  1014. }]];
  1015. [alert addAction:[UIAlertAction actionWithTitle:@"重新拍照" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
  1016. _currentStep.status = ClickStatusMid;
  1017. [self performSelector:@selector(takePhotoByBlueTooth) withObject:nil afterDelay:1];
  1018. }]];
  1019. [self presentViewController:alert animated:NO completion:nil];
  1020. }
  1021. - (void)wantSignOut{
  1022. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"您确定要结束当前模拟计时训练吗?" preferredStyle:UIAlertControllerStyleAlert];
  1023. [alertFind addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  1024. _step6.status = ClickStatusHighlighted;
  1025. }]];
  1026. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1027. self.currentStep = _step6;
  1028. //先关闭定时器
  1029. if (timer.isValid) {
  1030. [timer invalidate];
  1031. timer = nil;
  1032. myDelegate.mnPeriodVC = nil;
  1033. }
  1034. //关闭蓝牙连接
  1035. [self closeBlueTooth];
  1036. [gatherTrainDic setValue:@"2" forKey:@"isOver"];
  1037. [self saveGatherTrainDic];
  1038. minuteTrainArray = [DB_Helper quearyTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  1039. if (minuteTrainArray.count > 0) {
  1040. [self uploadAppTrainPlWithSignOut:YES];
  1041. }else{
  1042. [self signOut];
  1043. }
  1044. }]];
  1045. [self presentViewController:alertFind animated:true completion:nil];
  1046. }
  1047. - (void)removeImage {
  1048. if (_photoImg) {
  1049. [_photoImg removeFromSuperview];
  1050. }
  1051. }
  1052. #pragma mark - 蓝牙写入数据
  1053. - (void)sendBlueToothOrder{
  1054. //不同蓝牙指令重置失败次数
  1055. if (blueToothOrder != blueToothOrder_old) {
  1056. blueToothOrder_old = blueToothOrder;
  1057. reOrderCount = 0;
  1058. }
  1059. lackVertifyTime = 0;
  1060. dataIsLack = NO;
  1061. lastData = nil;
  1062. if (blueToothOrder && blueToothOrder.length != 0) {
  1063. [self.peripheral writeValue:[Tools convertHexStrToData:blueToothOrder] forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
  1064. }
  1065. }
  1066. #pragma mark - 拍照命令
  1067. //发送拍照命令
  1068. - (void)takePhotoByBlueTooth
  1069. {
  1070. imageData = @"";
  1071. lastData = @"";
  1072. imgLongth = 0;
  1073. reOrderCount = 0;
  1074. //发送拍照指令
  1075. [Tools playAudioWithString:@"拍照准备"];
  1076. dispatch_time_t zz = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
  1077. dispatch_after(zz, dispatch_get_main_queue(), ^{
  1078. blueToothOrder = kTakePhotoOrder;
  1079. [self sendBlueToothOrder];//每次发送蓝牙指令lackVertifyTime = 0;
  1080. });
  1081. if (!signInTimer) {
  1082. signInTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(signInTimerRun) userInfo:nil repeats:YES];
  1083. }
  1084. }
  1085. //此定时器仅存在于拍照的整个过程
  1086. - (void)signInTimerRun {
  1087. if (isConnectBlueTooth) {
  1088. //每一包分批次等待时间
  1089. lackVertifyTime++;
  1090. if (dataIsLack && lackVertifyTime == 5) {
  1091. reOrderCount ++;
  1092. if (reOrderCount < 3) {
  1093. [self sendBlueToothOrder];//重发拍照/获取图片相关指令
  1094. }else {
  1095. //apap蓝牙指令交流失败
  1096. showMsgByAlert(self, @"拍照失败!");
  1097. [Tools playAudioWithString:@"拍照失败!"];
  1098. _currentStep.status = ClickStatusHighlighted;
  1099. }
  1100. }
  1101. }
  1102. }
  1103. - (void)closeBlueTooth {
  1104. if (peripheralName.length > 0) {
  1105. //断开连接
  1106. if (self.peripheral) {
  1107. [self.cbcManager cancelPeripheralConnection:self.peripheral];
  1108. }
  1109. self.peripheral.delegate = nil;
  1110. self.peripheral = nil;
  1111. self.writeCharacteristic = nil;
  1112. self.readCharacteristic = nil;
  1113. self.cbcManager = nil;
  1114. }
  1115. }
  1116. #pragma mark - 模拟设备升级
  1117. - (void)startUpdateMNQDevWithFilePath:(NSURL *)filePath{
  1118. MBProgressHUD *hud = [MBProgressHUD HUDForView:self.view];
  1119. hud.label.text = @"模拟设备升级中";
  1120. hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
  1121. NSData *data = [NSData dataWithContentsOfURL:filePath];
  1122. packgeHexStr = [Tools convertDataToHexStr:data];
  1123. [self sendMsgWithSubPackageWithHexString:@"be00040b01eded"];//这里模仿外设请求第一包
  1124. }
  1125. -(void)sendMsgWithSubPackageWithHexString:(NSString *)hexStr{
  1126. NSString *orderStr = [hexStr substringWithRange:NSMakeRange(8, 2)];
  1127. NSInteger order = [Tools getHexNumWithString:orderStr];
  1128. NSString *perHexString = nil;
  1129. MBProgressHUD *hud = [MBProgressHUD HUDForView:self.view];
  1130. if (packgeHexStr.length == 0) {
  1131. //升级完成
  1132. perHexString = [NSString stringWithFormat:@"be00048b00eded"];
  1133. hud.progress = 1.0;
  1134. dispatch_async(dispatch_get_main_queue(), ^{
  1135. [MBProgressHUD hideHUDForView:self.view];
  1136. });
  1137. }else if (packgeHexStr.length > order*1792) {
  1138. perHexString = [NSString stringWithFormat:@"be%.4x8b%@%@eded",BLE_SEND_PER_LEN,orderStr,[packgeHexStr substringWithRange:NSMakeRange(order*1792-1792, 1792)]];
  1139. hud.progress = 1.0*order/(packgeHexStr.length/1792);
  1140. }else{
  1141. NSString *newStr = [packgeHexStr substringWithRange:NSMakeRange(order*1792-1792, packgeHexStr.length%1792)];
  1142. perHexString = [NSString stringWithFormat:@"be%.4lx8b%@%@eded",packgeHexStr.length%1792/2+4,orderStr,newStr];
  1143. packgeHexStr = @"";
  1144. }
  1145. NSLog(@"升级包==> : %ld == %@ ",perHexString.length,perHexString);
  1146. [self sendMsgWithSubPackage:[Tools convertHexStrToData:perHexString]];
  1147. }
  1148. //分包发送蓝牙数据
  1149. -(void)sendMsgWithSubPackage:(NSData*)msgData
  1150. {
  1151. for (int i = 0; i < [msgData length]; i += BLE_SEND_MAX_LEN) {
  1152. // 预加 最大包长度,如果依然小于总数据长度,可以取最大包数据大小
  1153. if ((i + BLE_SEND_MAX_LEN) < [msgData length]) {
  1154. NSString *rangeStr = [NSString stringWithFormat:@"%i,%i", i, BLE_SEND_MAX_LEN];
  1155. NSData *subData = [msgData subdataWithRange:NSRangeFromString(rangeStr)];
  1156. NSLog(@"升级小小包subData:%@",subData);
  1157. [self.peripheral writeValue:subData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithoutResponse];// 这里之所以用这个属性是为了提升发送速度。用带回应的也能写入成功 只是因为回调结果导致过程太慢
  1158. //根据接收模块的处理能力做相应延时
  1159. usleep(20 * 1000);
  1160. }
  1161. else {
  1162. NSString *rangeStr = [NSString stringWithFormat:@"%i,%i", i, (int)([msgData length] - i)];
  1163. NSData *subData = [msgData subdataWithRange:NSRangeFromString(rangeStr)];
  1164. NSLog(@"升级小小包subData:%@",subData);
  1165. [self.peripheral writeValue:subData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithoutResponse];
  1166. usleep(20 * 1000);
  1167. }
  1168. }
  1169. }
  1170. #pragma mark - 添加本地通知
  1171. -(void)addLocalNotificationWithAudio:(NSString *)audioName{
  1172. //定义本地通知对象
  1173. UILocalNotification *notification = [[UILocalNotification alloc]init];
  1174. //设置调用时间
  1175. notification.fireDate = [NSDate date];
  1176. //设置通知属性
  1177. NSString *bodyString = @"优易学车提醒您";
  1178. if ([audioName isEqualToString:@"mn_takePhotoColloctPic.mp3"]) {
  1179. bodyString = @"拍照已完成,正在采集照片数据";//其实这个mp3 感觉也是没太必要的。这个时候都没计时 学员应该多关注一些手机,app在前台才对
  1180. }else if ([audioName isEqualToString:@"mn_blueToothConnect.mp3"]) {
  1181. bodyString = @"模拟设备连接成功";
  1182. }else if ([audioName isEqualToString:@"mn_blueToothDisconnect.mp3"]) {
  1183. bodyString = @"模拟设备已断开,计时暂停,正在尝试重新连接";
  1184. }else if ([audioName isEqualToString:@"mn_blueToothQuit.mp3"]) {
  1185. bodyString = @"模拟计时已签退,本次计时结束";
  1186. }else if ([audioName isEqualToString:@"mn_processVerification.mp3"]){
  1187. bodyString = @"计时停止,请进行拍照验证";
  1188. }
  1189. notification.alertBody = bodyString; //通知主体
  1190. notification.applicationIconBadgeNumber = 1;//应用程序图标右上角显示的消息数
  1191. notification.alertAction = @"打开应用"; //待机界面的滑动动作提示
  1192. notification.soundName = audioName;//收到通知时播放的声音,默认消息声音
  1193. NSDictionary *userDict = [NSDictionary dictionaryWithObjectsAndKeys:@"nothing",@"MNQTrainVC", nil];
  1194. notification.userInfo = userDict;
  1195. //调用通知
  1196. [[UIApplication sharedApplication] scheduleLocalNotification:notification];
  1197. }
  1198. #pragma mark gps定位相关
  1199. -(void)setGPSLocationService{
  1200. //实例化manager
  1201. locationManager=[[CLLocationManager alloc]init];
  1202. [locationManager requestWhenInUseAuthorization];
  1203. //设置代理
  1204. locationManager.delegate=self;
  1205. //设置定位精度
  1206. //定位要求的精度越高、属性distanceFilter的值越小,应用程序的耗电量就越大。
  1207. locationManager.desiredAccuracy=kCLLocationAccuracyNearestTenMeters;
  1208. //定位距离
  1209. locationManager.distanceFilter=50; //这里定位只是为了使其后台运行
  1210. //申请定位许可,iOS8以后特有
  1211. /** 由于IOS8中定位的授权机制改变 需要进行手动授权
  1212. * 获取授权认证,两个方法:
  1213. * [self.locationManager requestWhenInUseAuthorization];
  1214. * [self.locationManager requestAlwaysAuthorization];
  1215. */
  1216. if([locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
  1217. [locationManager requestAlwaysAuthorization];
  1218. }
  1219. if ([[UIDevice currentDevice].systemVersion floatValue] > 9)
  1220. {
  1221. /** iOS9新特性:将允许出现这种场景:同一app中多个location manager:一些只能在前台定位,另一些可在后台定位(并可随时禁止其后台定位)。 */
  1222. [locationManager setAllowsBackgroundLocationUpdates:YES];
  1223. }
  1224. //开始定位
  1225. [locationManager startUpdatingLocation];
  1226. }
  1227. - (void)locationManager:(CLLocationManager *)manager
  1228. didFailWithError:(NSError *)error{
  1229. /*
  1230. kCLErrorDenied,//访问位置或范围已被用户拒绝
  1231. kCLErrorNetwork,//一般,与网络相关的错误
  1232. */
  1233. NSLog(@"位置访问失败");
  1234. if (!RQ_AD_MANAGER.isFirstAppLoad) {
  1235. ShowMsg(@"请在iPhone的“设置”-“隐私”-“定位服务”功能中,找到“极速驾培”打开位置访问权限");
  1236. // [RQ_SHARE_FUNCTION showAlertWithTitle:@"温馨提示" message:@"请在iPhone的“设置”-“隐私”-“定位服务”功能中,找到“极速驾培”打开位置访问权限" alertControllerStyle:UIAlertControllerStyleAlert cancelButtonTitle:@"确定" otherButtonTitles:nil otherButtonStyles:nil showInWindow:NO completion:nil];
  1237. }
  1238. [locationManager stopUpdatingLocation];
  1239. }
  1240. - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(nonnull NSArray<CLLocation *> *)locations {
  1241. CLLocation *loctaion = [locations firstObject];
  1242. CLLocationCoordinate2D coordinate = loctaion.coordinate;
  1243. myCoordinate = BMKCoordTrans(coordinate, BMK_COORDTYPE_GPS, BMK_COORDTYPE_BD09LL);//转换GPS坐标至百度坐标(加密后的坐标)
  1244. }
  1245. //#pragma mark 打开相机扫描二维码
  1246. //-(void)openScan{
  1247. // ScanVC *scan = [[ScanVC alloc] init];
  1248. // scan.type = @"";
  1249. // [scan scanBlock:^(NSString *dataString) {
  1250. // if ([dataString isEqualToString:@"success"]) {
  1251. // myDelegate.mnTrainType = @"2";
  1252. // [self getBlueToothAndDevOrder];
  1253. // }
  1254. // }];
  1255. // [self navPushHideTabbarToVC:scan];
  1256. //}
  1257. #pragma mark - 本地数据读取
  1258. - (void)saveGatherTrainDic
  1259. {
  1260. NSString *filePath = [Tools getPathWithFileName:@"MNQTrainDic.plist"];
  1261. [gatherTrainDic writeToFile:filePath atomically:YES];
  1262. }
  1263. - (void)getGatherTrainDic
  1264. {
  1265. NSString *filePath = [Tools getPathWithFileName:@"MNQTrainDic.plist"];
  1266. if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  1267. gatherTrainDic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
  1268. }
  1269. }
  1270. #pragma mark tableview
  1271. -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  1272. {
  1273. return dataSourceArray.count;
  1274. }
  1275. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  1276. {
  1277. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
  1278. if (cell == nil) {
  1279. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
  1280. cell.selectionStyle = UITableViewCellSelectionStyleNone;
  1281. }
  1282. [cell.textLabel setText:[dataSourceArray[dataSourceArray.count - indexPath.row - 1] substringFromIndex:1] Font:Font17 TextColor:[[dataSourceArray[dataSourceArray.count - indexPath.row - 1] substringToIndex:1] isEqualToString:@"1"]?[UIColor redColor]:kTitleColor];
  1283. [cell.textLabel setNumberOfLines:0];
  1284. return cell;
  1285. }
  1286. #pragma mark -选择科目
  1287. - (void)slelectTrainSubject{
  1288. BOOL isOpenTwo = [RQ_USER_MANAGER.currentUser.mnqTwoOpen isEqualToString:@"1"];
  1289. BOOL isOpenThree = [RQ_USER_MANAGER.currentUser.mnqThreeOpen isEqualToString:@"1"];
  1290. //同时开启
  1291. if (isOpenTwo && isOpenThree) {
  1292. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"请选择训练科目" preferredStyle:UIAlertControllerStyleAlert];
  1293. [alertFind addAction:[UIAlertAction actionWithTitle:@"科二" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  1294. subject = @"2";
  1295. self.navigationItem.title = [NSString stringWithFormat:@"科目%@模拟计时",[subject isEqualToString:@"2"]?@"二":@"三"];
  1296. [self getBlueToothAndDevOrder];
  1297. }]];
  1298. [alertFind addAction:[UIAlertAction actionWithTitle:@"科三" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1299. subject = @"3";
  1300. self.navigationItem.title = [NSString stringWithFormat:@"科目%@模拟计时",[subject isEqualToString:@"2"]?@"二":@"三"];
  1301. [self getBlueToothAndDevOrder];
  1302. }]];
  1303. [self presentViewController:alertFind animated:true completion:nil];
  1304. }else if (isOpenTwo || isOpenThree){
  1305. //其中一个开启
  1306. subject = isOpenTwo ? @"2" : @"3";
  1307. self.navigationItem.title = [NSString stringWithFormat:@"科目%@模拟计时",[subject isEqualToString:@"2"]?@"二":@"三"];
  1308. [self getBlueToothAndDevOrder];
  1309. }else{
  1310. //都未开启
  1311. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"暂未开启模拟计时" preferredStyle:UIAlertControllerStyleAlert];
  1312. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1313. [self goBackByNavigation];
  1314. }]];
  1315. [self presentViewController:alertFind animated:true completion:nil];
  1316. }
  1317. }
  1318. #pragma mark - 继续计时
  1319. - (void)goonTrain{
  1320. //蓝牙
  1321. peripheralName = gatherTrainDic[@"blueName"];
  1322. if (peripheralName.length > 0) {
  1323. //如果之前有蓝牙 现在也要重新开启起来
  1324. [self searchBlueTooth];
  1325. }else{
  1326. [self getBlueToothAndDevOrder];
  1327. return;
  1328. }
  1329. }
  1330. #pragma mark 开始计时
  1331. - (void)beginTimer {
  1332. //更新表统计
  1333. [self reloadDetailLab];
  1334. //过程验证计时
  1335. photoSeconds = 0;
  1336. self.currentStep = _step5;
  1337. //过程中不需要再发送重要指令。该定时器主要处理拍照命令
  1338. if (signInTimer) {
  1339. [signInTimer invalidate];
  1340. signInTimer = nil;
  1341. }
  1342. myDelegate.mnTrainType = @"0";
  1343. [self addBlueToothLabelWithContent:@"签到成功,开始计时" mp3:nil];
  1344. [Tools playAudioWithString:[NSString stringWithFormat:@"开始训练,训练过程中允许手机黑屏,请不要关闭应用,否则会影响学时上传,祝您体验愉快"]];
  1345. //将之前的学时全部清空
  1346. [DB_Helper deleteAllTrainWithType:mnTableName];
  1347. [gatherTrainDic setValue:subject forKey:@"subject"];//训练科目
  1348. [gatherTrainDic setValue:peripheralName forKey:@"blueName"];//蓝牙名
  1349. [gatherTrainDic setValue:classId forKey:@"classId"];//课堂id
  1350. [gatherTrainDic setValue:classCode forKey:@"classCode"];//课程编码
  1351. [gatherTrainDic setValue:trainNum forKey:@"trainNum"];//学时编号
  1352. [gatherTrainDic setValue:@"0" forKey:@"photoSeconds"];//秒 服务器训练状态返回接口(登录完之后调用)/以及人脸比对接口会返回更新该值
  1353. [gatherTrainDic setValue:@"0" forKey:@"isOver"];//是否结束
  1354. [self saveGatherTrainDic];//保存至本地 可以用于断线重连
  1355. //classId , classCode , trainNum
  1356. minuteTrainDic = [NSMutableDictionary dictionary];
  1357. [minuteTrainDic setValue:classId forKey:@"classId"];
  1358. [minuteTrainDic setValue:classCode forKey:@"classCode"];
  1359. [minuteTrainDic setValue:trainNum forKey:@"trainNum"];
  1360. disconnectSeconds = 0;
  1361. timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
  1362. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];//处理UI交互阻塞计时
  1363. trainView.hidden = NO;
  1364. myDelegate.mnPeriodVC = self;
  1365. }
  1366. - (void)timerRun
  1367. {
  1368. NSLog(@"timerRuning");
  1369. //计时过程中蓝牙断开连接
  1370. if (!isConnectBlueTooth && peripheralName.length > 0) {
  1371. disconnectSeconds ++;
  1372. //如果超过10分钟 停止每5s自动连接
  1373. if (disconnectSeconds >= 10*60) {
  1374. return;
  1375. }
  1376. if (disconnectSeconds%5 == 0) {
  1377. if (_cbcManager && self.peripheral) {
  1378. //[Tools playAudioWithString:@"设备连接中,请稍等"];这个在中途断开的时候 还是不要提示了
  1379. [_cbcManager connectPeripheral:self.peripheral options:nil];
  1380. }
  1381. }
  1382. return;
  1383. }
  1384. //每processVerificationTime进行过程验证 photoSeconds!= 0 第一次进来的时候规避掉过程验证
  1385. if (photoSeconds !=0 && processVerificationTime != 0 && onProcessVerification == NO) {//processVerificationTime不等于0表示需要验证
  1386. if (photoSeconds%(processVerificationTime*60) == 0) {
  1387. onProcessVerification = YES;
  1388. processVerificationFailureCount = 0;
  1389. /*是否停止计时器
  1390. 为了防止学员在收到提醒以后并且长时间(>3s)离开了蓝牙连接范围,为了走前面的每5s自动连接蓝牙重连操作,所以还是不停止计时器了
  1391. */
  1392. _currentStep = _step4;//不触发setCurrentStep 步骤的方法
  1393. _step5.status = ClickStatusHaveDone;
  1394. #if 0
  1395. //手机通知学员
  1396. [self addBlueToothLabelWithContent:@"计时停止,请进行拍照验证" mp3:@"mn_processVerification.mp3"];
  1397. //让学员发起过程验证拍照指令
  1398. _step4.status = ClickStatusHighlighted;
  1399. UIAlertView *servicesDisabledAlert = [[UIAlertView alloc] initWithTitle:@"过程验证" message:@"模拟计时已停止,请前往 个人-模拟计时 进行人脸验证" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
  1400. [servicesDisabledAlert show];
  1401. #else
  1402. //手机不通知学员,添加日志记录
  1403. NSString *string = @"计时停止,请进行拍照验证";
  1404. [self addLogsWithConten:string status:4396];
  1405. _statusLab.text = string;
  1406. //这种直接发起拍照命令的方式,符合过程验证“抓拍”的意义。但是鉴于只有app向学员传达了训练状态这一点,还是让学员主动操作手机进行过程验证吧。如果需要改的话 就用下面这种方式
  1407. /*5s是为了等待前面的语音提示完成,避免与拍照语音提示冲突;
  1408. 这里是否要考虑如果是过程验证拍照,想要在手机黑屏的情况下让学员完成验证过程,怎么做比较好呢?该怎么和学员交流呢
  1409. */
  1410. _step4.status = ClickStatusMid;
  1411. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1412. [self takePhotoByBlueTooth];
  1413. });
  1414. #endif
  1415. }
  1416. }
  1417. //过程验证,停止计时
  1418. if (onProcessVerification) {
  1419. return;
  1420. }
  1421. //计时器正常运作(蓝牙一定是连接的,且不处于过程验证状态)
  1422. seconds++;
  1423. photoSeconds++;
  1424. NSLog(@"%ld---->最新蓝牙指令:%@ ---->拍照时间:%ld",seconds,blueToothOrder,photoSeconds);
  1425. //每一分钟记录上传一次学时
  1426. if (seconds%60 == 0) {
  1427. //生成新的trainNum 先取之前的(签到时候的或者上一分钟的) 后四位序列码+1。顺便存入userDefault
  1428. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  1429. int OrderNum = [[[[userDefaults valueForKey:@"mnTrainOrderNum"] componentsSeparatedByString:@","] lastObject] intValue] + 1;
  1430. NSString *trainString = @"";
  1431. if (OrderNum < 10) {
  1432. trainString = [NSString stringWithFormat:@"000%d",OrderNum];
  1433. }else if (OrderNum < 100){
  1434. trainString = [NSString stringWithFormat:@"00%d",OrderNum];
  1435. }else if (OrderNum < 1000){
  1436. trainString = [NSString stringWithFormat:@"0%d",OrderNum];
  1437. }else{
  1438. trainString = [NSString stringWithFormat:@"%d",OrderNum];
  1439. }
  1440. trainNum = [NSString stringWithFormat:@"%@%@",[trainNum substringToIndex:trainNum.length - 4],trainString];
  1441. //将trainOrderNum写入NSUserDefaults
  1442. trainOrderNum = [NSString stringWithFormat:@"%@,%d",[[[userDefaults valueForKey:@"mnTrainOrderNum"] componentsSeparatedByString:@","] firstObject],OrderNum];
  1443. [userDefaults setValue:trainOrderNum forKey:@"mnTrainOrderNum"];
  1444. //这个点就当前时间
  1445. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  1446. NSString *dateString = [dateFormatter stringFromDate:[NSDate date]];
  1447. [minuteTrainDic setValue:dateString forKey:@"gps"];//这里不想新增表字段了 并且正常情况应该是gps里面包含了dateString。这里为了存取方便就。。
  1448. [minuteTrainDic setValue:trainNum forKey:@"trainNum"];
  1449. NSDictionary *dict = [NSDictionary dictionaryWithDictionary:minuteTrainDic];
  1450. [DB_Helper saveTrainWithDic:dict type:mnTableName];
  1451. //更新gatherTrainDic数据 并存入本地
  1452. [gatherTrainDic setValue:[NSString stringWithFormat:@"%d",(int)photoSeconds] forKey:@"photoTime"];
  1453. [gatherTrainDic setValue:trainNum forKey:@"trainNum"];
  1454. [self saveGatherTrainDic];
  1455. minuteTrainArray = [DB_Helper quearyTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  1456. [self uploadAppTrainPlWithSignOut:NO];
  1457. }
  1458. //更新UI上的时间
  1459. [self upDateTime];
  1460. }
  1461. - (void)getClassIdAndSoOn
  1462. {
  1463. //classId
  1464. NSTimeInterval timeInterval = [[NSDate date] timeIntervalSince1970]*1000;
  1465. // NSString *timeString = [[[[NSString stringWithFormat:@"%f",timeInterval] substringFromIndex:1] componentsSeparatedByString:@"."] firstObject];
  1466. // NSInteger redomNum = arc4random()%8999 + 1000;
  1467. // classId = [NSString stringWithFormat:@"%d%@",(int)redomNum,timeString];
  1468. //13位整数+.+小数。其中第十位刚好是秒
  1469. classId = [[NSString stringWithFormat:@"%lf",timeInterval] substringToIndex:10];
  1470. //课程编码 classCode
  1471. NSString *carTypeString = @"21";
  1472. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"A1"]) {
  1473. carTypeString = @"01";
  1474. }
  1475. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"A2"]) {
  1476. carTypeString = @"02";
  1477. }
  1478. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"A3"]) {
  1479. carTypeString = @"03";
  1480. }
  1481. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"B1"]) {
  1482. carTypeString = @"11";
  1483. }
  1484. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"B2"]) {
  1485. carTypeString = @"12";
  1486. }
  1487. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"C1"]) {
  1488. carTypeString = @"21";
  1489. }
  1490. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"C2"]) {
  1491. carTypeString = @"22";
  1492. }
  1493. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"C3"]) {
  1494. carTypeString = @"23";
  1495. }
  1496. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"C4"]) {
  1497. carTypeString = @"24";
  1498. }
  1499. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"C5"]) {
  1500. carTypeString = @"25";
  1501. }
  1502. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"D"]) {
  1503. carTypeString = @"31";
  1504. }
  1505. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"E"]) {
  1506. carTypeString = @"32";
  1507. }
  1508. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"F"]) {
  1509. carTypeString = @"33";
  1510. }
  1511. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"M"]) {
  1512. carTypeString = @"41";
  1513. }
  1514. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"N"]) {
  1515. carTypeString = @"42";
  1516. }
  1517. if ([RQ_USER_MANAGER.currentUser.carType isEqualToString:@"P"]) {
  1518. carTypeString = @"43";
  1519. }
  1520. classCode = [NSString stringWithFormat:@"3%@%@120000",carTypeString,subject];
  1521. //学时编号 trainNum
  1522. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  1523. formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
  1524. [formatter setDateFormat:@"yyMMdd"];
  1525. NSString *dateString = [formatter stringFromDate:[NSDate date]];
  1526. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  1527. trainOrderNum = [userDefaults valueForKey:@"mnTrainOrderNum"]; //170209,20
  1528. //![trainOrderNum isKindOfClass:[NSString class]]
  1529. //判断是否是同一天
  1530. if (trainOrderNum == nil || ![[[trainOrderNum componentsSeparatedByString:@","] firstObject] isEqualToString:dateString]) {
  1531. [userDefaults setValue:[NSString stringWithFormat:@"%@,0",dateString] forKey:@"mnTrainOrderNum"];
  1532. }
  1533. trainOrderNum = [userDefaults valueForKey:@"mnTrainOrderNum"];
  1534. NSString *trainString = @"";
  1535. int trainInt = [[[trainOrderNum componentsSeparatedByString:@","] lastObject] intValue];
  1536. if (trainInt < 10) {
  1537. trainString = [NSString stringWithFormat:@"000%d",trainInt];
  1538. }else if (trainInt < 100){
  1539. trainString = [NSString stringWithFormat:@"00%d",trainInt];
  1540. }else if (trainInt < 1000){
  1541. trainString = [NSString stringWithFormat:@"0%d",trainInt];
  1542. }else{
  1543. trainString = [NSString stringWithFormat:@"%d",trainInt];
  1544. }
  1545. trainNum = [NSString stringWithFormat:@"%@%@%@",mnqDev.devnum,dateString,trainString];
  1546. }
  1547. #pragma mark 网络请求
  1548. //获取蓝牙名/序列号
  1549. - (void)getBlueToothAndDevOrder{
  1550. NSMutableArray *arr=[NSMutableArray array];
  1551. [arr addPro:@"stuId" Value:RQ_USER_MANAGER.currentUser.outId];
  1552. [arr addPro:@"dqbh" Value:RQ_USER_MANAGER.currentUser.city];
  1553. NSString* method = @"getStuCardIdOrBluetooth";
  1554. [MBProgressHUD showMessage:@"正在获取模拟设备信息" ToView:self.view];
  1555. [jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary * root) {
  1556. [MBProgressHUD hideHUDForView:self.view];
  1557. if (!root) {
  1558. ShowMsg(@"请求失败");
  1559. _currentStep.status = ClickStatusHighlighted;
  1560. return;
  1561. }
  1562. if (![root[@"code"] isEqualToString:@"0"]) {
  1563. showMsgByAlert(self, root[@"msg"]);
  1564. _currentStep.status = ClickStatusHighlighted;
  1565. return;
  1566. }
  1567. //获取到蓝牙名(序列号)搜索蓝牙连接
  1568. NSDictionary *dic = root[@"body"];
  1569. if (![dic isKindOfClass:[NSDictionary class]]) {
  1570. ShowMsg(@"数据异常");
  1571. _currentStep.status = ClickStatusHighlighted;
  1572. return;
  1573. }
  1574. mnqDev = [[MNQDev alloc]init];
  1575. mnqDev.devnum = dic[@"devnum"];
  1576. mnqDev.cardId = dic[@"cardId"];
  1577. mnqDev.carnum = dic[@"carnum"];
  1578. mnqDev.inscode = dic[@"inscode"];
  1579. mnqDev.mnqSn = dic[@"mnqSn"];
  1580. mnqDev.imei = dic[@"imei"];
  1581. peripheralName = mnqDev.carnum;
  1582. [self searchBlueTooth];
  1583. }];
  1584. }
  1585. //判断模拟设备版本号,是否需要升级
  1586. -(void)UpdateMNDev{
  1587. NSMutableArray *arr=[NSMutableArray array];
  1588. [arr addPro:@"versionCode" Value:nowVersion];
  1589. [arr addPro:@"dqbh" Value:RQ_USER_MANAGER.currentUser.city];
  1590. [arr addPro:@"imei" Value:mnqDev.imei];
  1591. [arr addPro:@"devnum" Value:mnqDev.devnum];
  1592. [arr addPro:@"inscode" Value:@""];
  1593. NSString* method = @"upgradesVersion";
  1594. [MBProgressHUD showMessage:@"正在检查模拟设备版本信息" ToView:self.view];
  1595. [jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary * root) {
  1596. [MBProgressHUD hideHUDForView:self.view];
  1597. if (!root) {
  1598. _currentStep.status = ClickStatusHighlighted;
  1599. [self addBlueToothLabelWithContent:@"模拟设备更新检查失败" mp3:nil];
  1600. ShowMsg(@"请求失败");
  1601. return;
  1602. }
  1603. if ([root[@"code"] isEqualToString:@"-1"]) {//这里和jiapeimanager里面的-1处理冲突了。就不管它吧,不是很重要。
  1604. _currentStep.status = ClickStatusHighlighted;
  1605. [self addBlueToothLabelWithContent:@"模拟设备更新检查失败" mp3:nil];
  1606. ShowMsg(root[@"msg"]);
  1607. return;
  1608. }
  1609. if ([root[@"code"] isEqualToString:@"1"]) {
  1610. //不需要更新
  1611. self.currentStep = _step3;
  1612. blueToothOrder = kGetDevNumAndStuCardOrder;
  1613. [self sendBlueToothOrder];
  1614. return;
  1615. }
  1616. //检测到新版本
  1617. NSString *urlStr = root[@"body"];
  1618. if ([root[@"code"] isEqualToString:@"0"]) {
  1619. if (urlStr && urlStr.length != 0) {
  1620. [self addBlueToothLabelWithContent:@"模拟设备升级中" mp3:nil];
  1621. [self downLoadNewDataWithurl:urlStr];
  1622. }
  1623. }
  1624. }];
  1625. }
  1626. -(void)downLoadNewDataWithurl:(NSString *)urlStr{
  1627. //创建传话管理者
  1628. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  1629. NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
  1630. //下载文件
  1631. /*
  1632. 第一个参数:请求对象
  1633. 第二个参数:progress 进度回调
  1634. 第三个参数:destination 回调(目标位置)
  1635. 有返回值
  1636. targetPath:临时文件路径
  1637. response:响应头信息
  1638. 第四个参数:completionHandler 下载完成后的回调
  1639. filePath:最终的文件路径
  1640. */
  1641. NSURLSessionDownloadTask *download = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
  1642. //下载进度
  1643. dispatch_async(dispatch_get_main_queue(), ^{
  1644. MBProgressHUD *hud = [MBProgressHUD HUDForView:self.view];
  1645. hud.label.text = [NSString stringWithFormat:@"正在下载模拟设备更新包(%.2f%%)",100.0 * (downloadProgress.completedUnitCount / downloadProgress.totalUnitCount)];
  1646. });
  1647. NSLog(@"%.2f",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);
  1648. }
  1649. destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
  1650. //保存的文件路径
  1651. NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
  1652. return [NSURL fileURLWithPath:fullPath];
  1653. }
  1654. completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
  1655. if (error) {
  1656. _currentStep.status = ClickStatusHighlighted;
  1657. NSLog(@"下载更新包出错:%@",error);
  1658. dispatch_async(dispatch_get_main_queue(), ^{
  1659. [MBProgressHUD hideHUDForView:self.view];
  1660. showMsgByAlert(self, @"下载更新包出错");
  1661. });
  1662. return;
  1663. }
  1664. NSLog(@"升级包filePath: %@",filePath);
  1665. if (filePath) {
  1666. [self startUpdateMNQDevWithFilePath:filePath];
  1667. }
  1668. }];
  1669. //执行Task
  1670. dispatch_async(dispatch_get_main_queue(), ^{
  1671. [MBProgressHUD showMessage:@"正在下载模拟设备更新包" ToView:self.view];
  1672. });
  1673. [download resume];
  1674. }
  1675. //人脸比对签到face2face/过程验证faceComparison onProcessVerification == yes 过程验证
  1676. - (void)upLoadPhotoWithImage:(UIImage *)image{
  1677. //蓝牙照片不作处理(调整图片像素)
  1678. UIImage *cutImg;
  1679. if (peripheralName.length > 0) {
  1680. cutImg = image;
  1681. }else {
  1682. //剪切 然后压缩图片
  1683. NSData *data = UIImageJPEGRepresentation([image scaledAndCutToSize:CGSizeMake(240, 320)],0.5);
  1684. cutImg = [UIImage imageWithData:data];
  1685. }
  1686. if (!myDelegate.isBackgroundTask) {
  1687. if (!_photoImg) {
  1688. _photoImg = [[UIImageView alloc] initWithFrame:CGRectMake(10, kSize.height - kNavOffSet - 200 - kSafeAreaBottomHeight, 200, 150)];
  1689. _photoImg.userInteractionEnabled = YES;
  1690. UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
  1691. [label setText:@"点击移除照片" Font:Font17 TextColor:[UIColor orangeColor] Alignment:NSTextAlignmentCenter];
  1692. [_photoImg addSubview:label];
  1693. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removeImage)];
  1694. [_photoImg addGestureRecognizer:tap];
  1695. }
  1696. [self.view addSubview:_photoImg];
  1697. _photoImg.image = cutImg;
  1698. [self performSelector:@selector(removeImage) withObject:nil afterDelay:10];
  1699. }
  1700. NSMutableArray *arr=[NSMutableArray array];
  1701. [arr addPro:@"dqbh" Value:RQ_USER_MANAGER.currentUser.city];
  1702. [arr addPro:@"photo" Value:[UIImageJPEGRepresentation(cutImg,1.0) base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]];//不带水印照片
  1703. [arr addPro:@"lng" Value:[NSString stringWithFormat:@"%f",myCoordinate.longitude]];
  1704. [arr addPro:@"lat" Value:[NSString stringWithFormat:@"%f",myCoordinate.latitude]];
  1705. [arr addPro:@"stuId" Value:RQ_USER_MANAGER.currentUser.outId];
  1706. [arr addPro:@"subject" Value:subject];
  1707. [arr addPro:@"subjcode" Value:classCode];
  1708. [arr addPro:@"classId" Value:classId];
  1709. NSString *method = onProcessVerification ? @"faceComparison" : @"face2face";
  1710. [MBProgressHUD showMessage:@"正在进行人脸比对" ToView:self.view];
  1711. [jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary * root) {
  1712. [MBProgressHUD hideHUDForView:self.view animated:YES];
  1713. blueToothOrder = @"";
  1714. if (!root) {
  1715. _currentStep.status = ClickStatusHighlighted;
  1716. if (![Util connectedToNetWork]) {
  1717. blueToothOrder = @"";
  1718. showMsgByAlert(self, @"请检查手机网络连接是否正常");
  1719. return;
  1720. }
  1721. [Tools playAudioWithString:@"人脸比对请求失败"];
  1722. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:@"人脸比对请求失败" preferredStyle:UIAlertControllerStyleAlert];
  1723. [alertFind addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
  1724. _currentStep.status = ClickStatusHighlighted;
  1725. }]];
  1726. [alertFind addAction:[UIAlertAction actionWithTitle:@"再试一次" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1727. _currentStep.status = ClickStatusMid;
  1728. [self upLoadPhotoWithImage:image];
  1729. }]];
  1730. [self presentViewController:alertFind animated:true completion:nil];
  1731. return;
  1732. }
  1733. if ([root[@"code"] isEqualToString:@"90"]) {
  1734. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:root[@"msg"] preferredStyle:UIAlertControllerStyleAlert];
  1735. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1736. [RQ_USER_MANAGER isShouldLogin];
  1737. }]];
  1738. [self presentViewController:alertFind animated:true completion:nil];
  1739. return;
  1740. }
  1741. if ([root[@"code"] isEqualToString:@"1"]) {
  1742. [Tools playAudioWithString:root[@"msg"]];
  1743. [self addBlueToothLabelWithContent:[NSString stringWithFormat:@"人脸验证失败"] mp3:nil];
  1744. if (onProcessVerification) {
  1745. //判断次数==0表示不限制失败次数,这个次数限制似乎就只有防止恶意调用接口的作用,减轻服务器压力?
  1746. if (processVerificationMaxCount != 0) {
  1747. processVerificationFailureCount++;
  1748. //达到最大次数
  1749. if (processVerificationFailureCount == processVerificationMaxCount) {
  1750. [self processVerificationSignOut];
  1751. return;
  1752. }
  1753. }
  1754. }
  1755. [self showAlertTakePhotoFailWithStr:root[@"msg"]];
  1756. return;
  1757. }
  1758. //过程验证不存在这种情况。就不管吧。但是为了防止服务器给一个惊喜 还是判断一下
  1759. //code = 2;已经签到成功了(可能因为上一次请求网络延迟,服务器已经签到成功,手机没有get到这个信息)
  1760. if (onProcessVerification && [root[@"code"] isEqualToString:@"2"]) {
  1761. [self getCurrentState];
  1762. return;
  1763. }
  1764. //code = 0;正常成功状态。
  1765. if ([root[@"code"] isEqualToString:@"0"]) {
  1766. if (onProcessVerification) {
  1767. //过程人脸比对成功 恢复计时;
  1768. photoSeconds = 0;//通过验证
  1769. onProcessVerification = NO;
  1770. self.currentStep = _step5;
  1771. //过程中不需要再发送重要指令。该定时器主要处理拍照命令
  1772. if (signInTimer) {
  1773. [signInTimer invalidate];
  1774. signInTimer = nil;
  1775. }
  1776. [self addBlueToothLabelWithContent:@"过程验证成功,恢复计时" mp3:nil];
  1777. [Tools playAudioWithString:[NSString stringWithFormat:@"过程验证成功,恢复计时"]];
  1778. }else{
  1779. //code = 0;正常成功状态。
  1780. //更新数据
  1781. [gatherTrainDic setValue:root[@"body"][@"CreditRation"] forKey:@"allNeed"];//总需学时
  1782. [gatherTrainDic setValue:root[@"body"][@"DurationSum"] forKey:@"allCompelet"];//已完成学时
  1783. CGFloat seconds = [[NSString stringWithFormat:@"%@",root[@"body"][@"Duration"]] floatValue]*60;
  1784. [gatherTrainDic setValue:[NSString stringWithFormat:@"%f",seconds] forKey:@"allTime"];//当天学时
  1785. [self saveGatherTrainDic];
  1786. //人脸比对成功服务器签到 开始计时
  1787. [self beginTimer];
  1788. }
  1789. }
  1790. }];
  1791. }
  1792. - (void)processVerificationSignOut{
  1793. self.currentStep = _step6;
  1794. //先关闭定时器
  1795. if (timer.isValid) {
  1796. [timer invalidate];
  1797. timer = nil;
  1798. myDelegate.mnPeriodVC = nil;
  1799. }
  1800. //关闭蓝牙连接
  1801. [self closeBlueTooth];
  1802. [gatherTrainDic setValue:@"2" forKey:@"isOver"];
  1803. [self saveGatherTrainDic];
  1804. minuteTrainArray = [DB_Helper quearyTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  1805. if (minuteTrainArray.count > 0) {
  1806. [self uploadAppTrainPlWithSignOut:YES];
  1807. }else{
  1808. [self signOut];
  1809. }
  1810. }
  1811. //上传分钟学时(批量) 这里要判断一下是否是最后签退过来的要做上传提示
  1812. - (void)uploadAppTrainPlWithSignOut:(BOOL)isSignOut
  1813. {
  1814. if (![Util connectedToNetWork]) {
  1815. if (isSignOut) {
  1816. _step6.status = ClickStatusHighlighted;
  1817. showMsgUnconnect();
  1818. }
  1819. [dateFormatter setDateFormat:@"HH:mm"];
  1820. NSString *timeString = [dateFormatter stringFromDate:[NSDate date]];
  1821. [dataSourceArray addObject:[NSString stringWithFormat:@"1%@ 学时上传失败!",timeString]];
  1822. [mainTableView reloadData];
  1823. return;
  1824. }
  1825. NSMutableArray *mArr = [NSMutableArray new];;
  1826. for (NSDictionary *dic in minuteTrainArray) {
  1827. NSMutableDictionary * csDic = [NSMutableDictionary new];
  1828. [csDic setValue:RQ_USER_MANAGER.currentUser.outId forKey:@"stuId"];
  1829. [csDic setValue:dic[@"trainNum"] forKey:@"trainNo"];
  1830. [csDic setValue:dic[@"classCode"] forKey:@"subjcode"];
  1831. [csDic setValue:dic[@"classId"] forKey:@"classId"];
  1832. [csDic setValue:dic[@"gps"] forKey:@"recordTime"];
  1833. [csDic setValue:RQ_USER_MANAGER.currentUser.city forKey:@"dqbh"];
  1834. [mArr addObject:csDic];
  1835. }
  1836. NSError *error = nil;
  1837. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mArr
  1838. options:NSJSONWritingPrettyPrinted
  1839. error:&error];
  1840. NSString *jsonString = nil;
  1841. if ([jsonData length] != 0 && error == nil){
  1842. // 使用这个方法的返回,我们就可以得到想要的JSON串
  1843. jsonString = [[NSString alloc] initWithData:jsonData
  1844. encoding:NSUTF8StringEncoding];
  1845. }else{
  1846. NSLog(@"数据解析异常:%@",error);
  1847. }
  1848. NSMutableArray *arr=[NSMutableArray array];
  1849. [arr addPro:@"trains" Value:jsonString];
  1850. NSString* method = @"uploadTrainMinPl";
  1851. if (isSignOut) {
  1852. [MBProgressHUD showMessage:@"正在补传学时" ToView:self.view];
  1853. [self addBlueToothLabelWithContent:@"正在补传学时" mp3:nil];
  1854. }
  1855. NSString *timeString = [dateFormatter stringFromDate:[NSDate date]];
  1856. [jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary * root) {
  1857. if (isSignOut) {
  1858. [MBProgressHUD hideHUDForView:self.view animated:YES];
  1859. }
  1860. [self.view setUserInteractionEnabled:YES];
  1861. [dateFormatter setDateFormat:@"HH:mm"];
  1862. if (!root) {
  1863. if (isSignOut) {
  1864. _currentStep.status = ClickStatusHighlighted;
  1865. [Tools playAudioWithString:@"学时补传失败"];
  1866. [self addBlueToothLabelWithContent:@"学时补传失败" mp3:nil];
  1867. }
  1868. [dataSourceArray addObject:[NSString stringWithFormat:@"1%@ 学时上传失败!",timeString]];
  1869. return ;
  1870. }
  1871. if ([root[@"code"] isEqualToString:@"1"]) {
  1872. if (isSignOut) {
  1873. _currentStep.status = ClickStatusHighlighted;
  1874. [Tools playAudioWithString:root[@"msg"]];
  1875. showMsgByAlert(self, root[@"msg"]);
  1876. [self addBlueToothLabelWithContent:[NSString stringWithFormat:@"学时补传失败:%@",root[@"msg"]] mp3:nil];
  1877. }
  1878. [dataSourceArray addObject:[NSString stringWithFormat:@"1%@ 学时上传失败!",timeString]];
  1879. return;
  1880. }
  1881. if ([root[@"code"] isEqualToString:@"2"]) {//超过规定时间 服务器通知签退
  1882. [self haveSignOutByUser:NO];
  1883. return;
  1884. }
  1885. [dataSourceArray addObject:[NSString stringWithFormat:@"0%@ 学时上传成功!",timeString]];
  1886. [DB_Helper deleteTrainWithClassId:gatherTrainDic[@"classId"] type:mnTableName];
  1887. if (isSignOut) {
  1888. [self addBlueToothLabelWithContent:@"学时补传成功" mp3:nil];
  1889. [self signOut];
  1890. }else{
  1891. [mainTableView reloadData];
  1892. }
  1893. }];
  1894. }
  1895. - (void)signOut{
  1896. if (![Util connectedToNetWork]) {
  1897. _step6.status = ClickStatusHighlighted;
  1898. showMsgUnconnect();
  1899. return;
  1900. }
  1901. NSMutableArray *arr=[NSMutableArray array];
  1902. [arr addPro:@"stuId" Value:RQ_USER_MANAGER.currentUser.outId];
  1903. [arr addPro:@"dqbh" Value:RQ_USER_MANAGER.currentUser.city];
  1904. [arr addPro:@"subjcode" Value:classCode];
  1905. [arr addPro:@"classId" Value:classId];//签退如果没有这个东西 这段学时会无效
  1906. [arr addPro:@"lng" Value:[NSString stringWithFormat:@"%f",myCoordinate.longitude]];
  1907. [arr addPro:@"lat" Value:[NSString stringWithFormat:@"%f",myCoordinate.latitude]];
  1908. NSString* method = @"uploadMnqSignOut";
  1909. [MBProgressHUD showLoadToView:self.view];
  1910. [jiaPeiManager requestAnythingWithURL:method array:arr data:nil completion:^(NSDictionary * root) {
  1911. [MBProgressHUD hideHUDForView:self.view animated:YES];
  1912. if (!root) {
  1913. _step6.status = ClickStatusHighlighted;
  1914. ShowMsg(@"请求失败");
  1915. [Tools playAudioWithString:@"请求失败"];
  1916. return ;
  1917. }
  1918. if ([root[@"code"] isEqualToString:@"90"]) {//缺少outid
  1919. UIAlertController *alertFind = [UIAlertController alertControllerWithTitle:nil message:root[@"msg"] preferredStyle:UIAlertControllerStyleAlert];
  1920. [alertFind addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
  1921. [RQ_USER_MANAGER isShouldLogin];
  1922. }]];
  1923. [self presentViewController:alertFind animated:true completion:nil];
  1924. return;
  1925. }
  1926. if ([root[@"code"] isEqualToString:@"1"]) {
  1927. _step6.status = ClickStatusHighlighted;
  1928. ShowMsg(root[@"msg"]);
  1929. [Tools playAudioWithString:root[@"msg"]];
  1930. return;
  1931. }
  1932. [self haveSignOutByUser:YES];
  1933. }];
  1934. }
  1935. //获取当前训练状态;
  1936. -(void)getCurrentState
  1937. {
  1938. [MBProgressHUD showMessage:@"正在获取当前训练状态" ToView:self.view];
  1939. //这个主要目的是为了更新本地学时编号等数据
  1940. [RQ_COMMON_MANAGER getCurrentStateWithCompleteBlock:^(NSDictionary *root) {
  1941. [MBProgressHUD hideHUDForView:self.view animated:NO];
  1942. if (!root) {
  1943. ShowMsg(@"请求失败");
  1944. return;
  1945. }
  1946. if ([root[@"code"] isEqualToString:@"0"]) {
  1947. /*模拟训练状态判断
  1948. 0 成功获取学员信息
  1949. 1 学员还未在模拟器签到
  1950. 2.已登录,未签到(未进行人脸验证)
  1951. 3 无法获取到当前训练科目(人脸比对验证通过,但是没有上传任何学时)
  1952. 4 隔日签到记录
  1953. 5 分钟学时科目异常
  1954. */
  1955. NSString *status = [NSString stringWithFormat:@"%@",root[@"body"][@"mn"][@"TYPE"]];
  1956. //获取到状态
  1957. if ([status integerValue] == 0) {
  1958. [self beginTimer];
  1959. }else{
  1960. //既然人脸比对结果提示已经在训练中了 就不该有其它状态,如果还是发生了,就作比对结果返回异常处理
  1961. [self showAlertTakePhotoFailWithStr:@"异常情况"];
  1962. }
  1963. }else{
  1964. ShowMsg(root[@"msg"]);
  1965. }
  1966. }];
  1967. }
  1968. #pragma mark - 签退成功后的操作
  1969. - (void)haveSignOutByUser:(BOOL)isByUser{
  1970. if (isByUser == NO) {//如果不是用户操作的签退
  1971. self.currentStep = _step6;
  1972. //先关闭定时器
  1973. if (timer.isValid) {
  1974. [timer invalidate];
  1975. timer = nil;
  1976. myDelegate.mnPeriodVC = nil;
  1977. }
  1978. //关闭蓝牙连接
  1979. [self closeBlueTooth];
  1980. [gatherTrainDic setValue:@"2" forKey:@"isOver"];
  1981. [self saveGatherTrainDic];
  1982. }
  1983. //语音提示
  1984. NSString *alertMessage = @"模拟计时已签退,本次计时结束";
  1985. if (processVerificationFailureCount == processVerificationMaxCount && processVerificationFailureCount != 0) {
  1986. alertMessage = [NSString stringWithFormat:@"本次计时,过程验证失败次数%ld次,计时结束",processVerificationFailureCount];
  1987. [self addBlueToothLabelWithContent:alertMessage mp3:@"mn_blueToothQuit.mp3"];
  1988. }else{
  1989. [self addBlueToothLabelWithContent:@"模拟计时已签退,本次计时结束" mp3:@"mn_blueToothQuit.mp3"];
  1990. }
  1991. [DB_Helper deleteAllTrainWithType:mnTableName];
  1992. //将本地状态置为结束
  1993. [gatherTrainDic setValue:@"1" forKey:@"isOver"];
  1994. [gatherTrainDic removeObjectForKey:@"classId"];
  1995. [gatherTrainDic removeObjectForKey:@"subject"];
  1996. [self saveGatherTrainDic];
  1997. //关闭学时状态显示
  1998. [dataSourceArray removeAllObjects];
  1999. [mainTableView reloadData];
  2000. trainView.hidden = YES;
  2001. //timeLabel.text = @"00:00:00" ;
  2002. //先关闭定时器
  2003. if (timer.isValid) {
  2004. [timer invalidate];
  2005. timer = nil;
  2006. }
  2007. myDelegate.mnPeriodVC = nil;
  2008. myDelegate.mnTrainType = @"1";
  2009. //清空detailLab(top)
  2010. //for (UILabel *detailLab in detailLabArr) {
  2011. //detailLab.text = @"";
  2012. //}
  2013. [RQ_SHARE_FUNCTION showAlertWithTitle:@"温馨提示" message:[NSString stringWithFormat:@"%@.\n(ps:手机蓝牙可以关掉啦)",alertMessage] alertControllerStyle:UIAlertControllerStyleAlert cancelButtonTitle:@"确定" otherButtonTitles:nil otherButtonStyles:nil showInWindow:NO completion:^(NSUInteger selectedOtherButtonIndex) {
  2014. [self goBackByNavigation];
  2015. }];
  2016. }
  2017. #pragma mark - 转json字符串
  2018. - (NSString *)getJsonStringWithObj:(NSObject *)jsonObj{
  2019. NSError *error = nil;
  2020. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObj
  2021. options:NSJSONWritingPrettyPrinted
  2022. error:&error];
  2023. NSString *jsonString = nil;
  2024. if ([jsonData length] != 0 && error == nil){
  2025. // 使用这个方法的返回,我们就可以得到想要的JSON串
  2026. jsonString = [[NSString alloc] initWithData:jsonData
  2027. encoding:NSUTF8StringEncoding];
  2028. }else{
  2029. ShowMsg(@"数据错误,请重试!");
  2030. }
  2031. return jsonString;
  2032. }
  2033. @end