Commit 60a29b2cbd4dd2a794268d5b8cea3da93a5d6a54

Authored by jason
1 parent e8cd4c09

feat:ota viewcontroller

HDFwear.xcodeproj/project.pbxproj
... ... @@ -291,8 +291,11 @@
291 291 84792A232B5FACAC00851211 /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 84792A202B5FACAB00851211 /* Utils.m */; };
292 292 84792A242B5FACAC00851211 /* RemoteStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 84792A1D2B5FACAB00851211 /* RemoteStatus.m */; };
293 293 84792A252B5FACAC00851211 /* OTAManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 84792A1B2B5FACAB00851211 /* OTAManager.m */; };
  294 + 84792A292B5FB05F00851211 /* BluetoothManager+OTA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84792A282B5FB05F00851211 /* BluetoothManager+OTA.swift */; };
294 295 847AF36E2B4CF35F00E3456E /* NewSleepModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847AF36D2B4CF35F00E3456E /* NewSleepModel.swift */; };
295 296 847B88EF2B5780CA00851EE7 /* NewIntensiveTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847B88EE2B5780CA00851EE7 /* NewIntensiveTimeModel.swift */; };
  297 + 847CA80F2B6147680051AEAF /* OTAUpdateViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 847CA80D2B6147680051AEAF /* OTAUpdateViewController.m */; };
  298 + 847CA8102B6147680051AEAF /* OTAUpdateViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 847CA80E2B6147680051AEAF /* OTAUpdateViewController.xib */; };
296 299 84DA399D2B4D3A5B008D34A9 /* NewGpsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DA399C2B4D3A5B008D34A9 /* NewGpsModel.swift */; };
297 300 B212F1FD2A14CE0400781D59 /* LaunchSetting.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B212F1FF2A14CE0400781D59 /* LaunchSetting.storyboard */; };
298 301 B212F2052A14D28E00781D59 /* LaunchMyBodySettingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B212F2042A14D28E00781D59 /* LaunchMyBodySettingVC.swift */; };
... ... @@ -682,8 +685,12 @@
682 685 84792A202B5FACAB00851211 /* Utils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Utils.m; sourceTree = "<group>"; };
683 686 84792A212B5FACAC00851211 /* RemoteStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RemoteStatus.h; sourceTree = "<group>"; };
684 687 84792A272B5FADE500851211 /* LETransceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LETransceiver.h; sourceTree = "<group>"; };
  688 + 84792A282B5FB05F00851211 /* BluetoothManager+OTA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BluetoothManager+OTA.swift"; sourceTree = "<group>"; };
685 689 847AF36D2B4CF35F00E3456E /* NewSleepModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSleepModel.swift; sourceTree = "<group>"; };
686 690 847B88EE2B5780CA00851EE7 /* NewIntensiveTimeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewIntensiveTimeModel.swift; sourceTree = "<group>"; };
  691 + 847CA80C2B6147680051AEAF /* OTAUpdateViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTAUpdateViewController.h; sourceTree = "<group>"; };
  692 + 847CA80D2B6147680051AEAF /* OTAUpdateViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTAUpdateViewController.m; sourceTree = "<group>"; };
  693 + 847CA80E2B6147680051AEAF /* OTAUpdateViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = OTAUpdateViewController.xib; sourceTree = "<group>"; };
687 694 847D1C4A2B009FAC0097A96E /* 20231111ReadMe.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = 20231111ReadMe.md; sourceTree = "<group>"; };
688 695 847D2DE42B3D575F001BA7EF /* 20240110ReadMe.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = 20240110ReadMe.md; sourceTree = "<group>"; };
689 696 84DA399C2B4D3A5B008D34A9 /* NewGpsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGpsModel.swift; sourceTree = "<group>"; };
... ... @@ -1142,6 +1149,7 @@
1142 1149 844959772B48EF900029E2E0 /* BluetoothManager+Deprecated.swift */,
1143 1150 6C1F987E279BF0AA00C0C3BA /* BluetoothManager+Set.swift */,
1144 1151 B279A3792A4AC2F800A7C7B6 /* BluetoothManager+Function.swift */,
  1152 + 84792A282B5FB05F00851211 /* BluetoothManager+OTA.swift */,
1145 1153 8473FB612B4BF1A200409148 /* TaskManager.swift */,
1146 1154 6C411EE4275DFF37009B2E02 /* Bluetooth+Types.swift */,
1147 1155 6C6051B92760843F00286B37 /* BleMessage.swift */,
... ... @@ -1332,6 +1340,9 @@
1332 1340 84792A1D2B5FACAB00851211 /* RemoteStatus.m */,
1333 1341 84792A1C2B5FACAB00851211 /* Utils.h */,
1334 1342 84792A202B5FACAB00851211 /* Utils.m */,
  1343 + 847CA80C2B6147680051AEAF /* OTAUpdateViewController.h */,
  1344 + 847CA80D2B6147680051AEAF /* OTAUpdateViewController.m */,
  1345 + 847CA80E2B6147680051AEAF /* OTAUpdateViewController.xib */,
1335 1346 );
1336 1347 path = OTA;
1337 1348 sourceTree = "<group>";
... ... @@ -1499,6 +1510,7 @@
1499 1510 6CFEEBAF2772F18400621863 /* SettingCell2.xib in Resources */,
1500 1511 6C5972C227D0B2780096FD04 /* Sound1.wav in Resources */,
1501 1512 6C28F55A27901CF900E74EA5 /* HRReportCell.xib in Resources */,
  1513 + 847CA8102B6147680051AEAF /* OTAUpdateViewController.xib in Resources */,
1502 1514 6C85FB69278D56DA007D309A /* BloodOxygenCell.xib in Resources */,
1503 1515 6CEA284727EB033E006A44AB /* MedalCell.xib in Resources */,
1504 1516 6C28F53E279017B200E74EA5 /* Mine.storyboard in Resources */,
... ... @@ -1656,6 +1668,7 @@
1656 1668 6CFE53F6277ADAA300520B00 /* ModifyNicknameVC.swift in Sources */,
1657 1669 6C0D9B962776BA8300E8C880 /* HealthRemindVC.swift in Sources */,
1658 1670 6CBB0CAA27B7B6FA009B27C1 /* GenerateBlankVC.swift in Sources */,
  1671 + 84792A292B5FB05F00851211 /* BluetoothManager+OTA.swift in Sources */,
1659 1672 6C2EE979277FF7120051413A /* HealthReportVC.swift in Sources */,
1660 1673 6CEA284D27EB1359006A44AB /* MedalDetailVC.swift in Sources */,
1661 1674 B279A37A2A4AC2F800A7C7B6 /* BluetoothManager+Function.swift in Sources */,
... ... @@ -1782,6 +1795,7 @@
1782 1795 6C0D9BA8277726EB00E8C880 /* FirmwareUpdateVC.swift in Sources */,
1783 1796 6C6F407D274382D900F9473C /* HeartRateCell.swift in Sources */,
1784 1797 6C1B7EAA27B7977000DB9D1C /* NFCBlankVC.swift in Sources */,
  1798 + 847CA80F2B6147680051AEAF /* OTAUpdateViewController.m in Sources */,
1785 1799 6CCB7FF8276C6C06009EF617 /* RecordHeaderView.swift in Sources */,
1786 1800 6C8F74EA2779B98700B8A1A7 /* UserInfoSettingVC.swift in Sources */,
1787 1801 B262F7C72A1BB71600B4DFD2 /* ZCStringPickerView.swift in Sources */,
... ...
HDFwear/HDFwear-Bridging-Header.h
1 1 //
2 2 // Use this file to import your target's public headers that you would like to expose to Swift.
3   -//
4 3 #import "OTAManager.h"
5 4 #import "LETransceiver.h"
  5 +#import "OTAUpdateViewController.h"
6 6 #import "MTKBleManager.h"
7 7 #import "FmpGattClient.h"
8 8 #import "test.h"
... ...
HDFwear/Mine/MineViewController.swift
... ... @@ -429,6 +429,10 @@ extension MineViewController: UITableViewDataSource, UITableViewDelegate {
429 429 }
430 430 }
431 431  
  432 + let archiveAction29 = UIAlertAction(title: "ota update", style: .default) { action in
  433 + BluetoothManager.shared.startOtaUpdate()
  434 + }
  435 +
432 436 alert.addAction(archiveAction0)
433 437 alert.addAction(archiveAction1)
434 438 alert.addAction(archiveAction2)
... ... @@ -456,6 +460,7 @@ extension MineViewController: UITableViewDataSource, UITableViewDelegate {
456 460 alert.addAction(archiveAction28a)
457 461 alert.addAction(archiveAction28b)
458 462 alert.addAction(archiveAction28c)
  463 + alert.addAction(archiveAction29)
459 464  
460 465 alert.addAction(UIAlertAction(title: "ๅ–ๆถˆ", style: .destructive, handler: nil))
461 466 present(alert, animated: true, completion: nil)
... ...
HDFwear/OTA/OTAManager.h
... ... @@ -11,6 +11,14 @@
11 11  
12 12 NS_ASSUME_NONNULL_BEGIN
13 13  
  14 +#define kMsgPeripheralFound @"msgPeripheralFound"
  15 +#define kMsgPeripheralConnected @"msgPeripheralConnected"
  16 +#define kMsgPeripheralDisconnected @"msgPeripheralDisconnected"
  17 +#define kMsgOTAStatus @"msgOTAStatus"
  18 +#define kMsgAudioPackageRecevied @"msgAudioPackageRecevied"
  19 +#define kMsgAudioSpeedRecevied @"msgAudioSpeedRecevied"
  20 +#define kMsgRemoteStatusRecevied @"msgRemoteStatusRecevied"
  21 +
14 22 typedef enum{
15 23 STATE_UNKNOWN = 0,
16 24 STATE_IDLE = 1,
... ...
HDFwear/OTA/OTAUpdateViewController.h
... ... @@ -8,8 +8,19 @@
8 8 #import <UIKit/UIKit.h>
9 9  
10 10 NS_ASSUME_NONNULL_BEGIN
11   -
  11 +typedef void(^CloseBlock)(void);
12 12 @interface OTAUpdateViewController : UIViewController
  13 +@property (weak, nonatomic) IBOutlet UILabel *labelBluetoothState;
  14 +@property (weak, nonatomic) IBOutlet UIButton *buttonPrepare;
  15 +@property (weak, nonatomic) IBOutlet UIButton *buttonStart;
  16 +@property (weak, nonatomic) IBOutlet UILabel *labelOTAVersion;
  17 +
  18 +@property (weak, nonatomic) CBPeripheral *currentPeripheral;
  19 +@property (weak, nonatomic) OTAManager *otaManager;
  20 +@property (strong, nonatomic) NSString *path;
  21 +@property (copy, nonatomic) CloseBlock closeBlock;
  22 +- (IBAction)didButtonStartClicked:(UIButton *)sender;
  23 +- (IBAction)didButtonPrepareClicked:(UIButton *)sender;
13 24  
14 25 @end
15 26  
... ...
HDFwear/OTA/OTAUpdateViewController.m
... ... @@ -5,27 +5,226 @@
5 5 // Created by admin on 2024/1/24.
6 6 //
7 7  
  8 +#import "HDFwear-Bridging-Header.h"
8 9 #import "OTAUpdateViewController.h"
  10 +#import "OTAManager.h"
  11 +#import "MBProgressHUD.h"
9 12  
10 13 @interface OTAUpdateViewController ()
11 14  
12 15 @end
13 16  
14   -@implementation OTAUpdateViewController
15   -
  17 +@implementation OTAUpdateViewController{
  18 + NSDate *beginTime;
  19 +
  20 + BOOL isUpgrading;
  21 +}
16 22 - (void)viewDidLoad {
17 23 [super viewDidLoad];
18   - // Do any additional setup after loading the view from its nib.
  24 + // Do any additional setup after loading the view, typically from a nib.
  25 +
  26 + isUpgrading = NO;
  27 +
  28 + _buttonStart.layer.borderWidth = 1.0f;
  29 + _buttonStart.layer.cornerRadius = 4.0f;
  30 + _buttonStart.layer.masksToBounds = YES;
  31 + _buttonPrepare.layer.borderWidth = 1.0f;
  32 + _buttonPrepare.layer.cornerRadius = 4.0f;
  33 + _buttonPrepare.layer.masksToBounds = YES;
  34 +
  35 +
  36 + if (self.currentPeripheral) {
  37 + _labelBluetoothState.text = @"ๅทฒ่ฟžๆŽฅ";
  38 + [_buttonPrepare setEnabled:YES];
  39 + } else {
  40 + _labelBluetoothState.text = @"N/A";
  41 + [_buttonPrepare setEnabled:NO];
  42 + [_buttonStart setEnabled:NO];
  43 + }
  44 +
  45 + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  46 + [nc addObserver:self
  47 + selector:@selector(handleGlobalMessage:)
  48 + name:kMsgPeripheralConnected
  49 + object:nil];
  50 + [nc addObserver:self
  51 + selector:@selector(handleGlobalMessage:)
  52 + name:kMsgPeripheralDisconnected
  53 + object:nil];
  54 + [nc addObserver:self
  55 + selector:@selector(handleGlobalMessage:)
  56 + name:kMsgOTAStatus
  57 + object:nil];
  58 + [nc addObserver:self
  59 + selector:@selector(handleGlobalMessage:)
  60 + name:kMsgAudioPackageRecevied
  61 + object:nil];
  62 + [nc addObserver:self
  63 + selector:@selector(handleGlobalMessage:)
  64 + name:kMsgAudioSpeedRecevied
  65 + object:nil];
  66 + [nc addObserver:self
  67 + selector:@selector(handleGlobalMessage:)
  68 + name:kMsgRemoteStatusRecevied
  69 + object:nil];
  70 +
  71 +}
  72 +
  73 +- (void)viewWillDisappear:(BOOL)animated {
  74 + [super viewWillDisappear:animated];
  75 + if (self.closeBlock) {
  76 + self.closeBlock();
  77 + }
  78 +}
  79 +
  80 +- (void)handleGlobalMessage:(NSNotification *)note{
  81 + NSString* name = note.name;
  82 + NSLog(@"nick_name:%@", name);
  83 + NSDictionary* dict = note.userInfo;
  84 +
  85 + if ([name isEqualToString:kMsgPeripheralConnected]){
  86 + if (self.currentPeripheral) {
  87 + _labelBluetoothState.text = @"ๅทฒ่ฟžๆŽฅ";
  88 + [_buttonPrepare setEnabled:YES];
  89 + NSInteger mtu = [self.currentPeripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse];
  90 + NSLog(@"mtu view:%d", mtu);
  91 + [self.otaManager setMaxMtu:mtu];
  92 + }
  93 + } else if ([name isEqualToString:kMsgPeripheralDisconnected]){
  94 + _labelBluetoothState.text = @"N/A";
  95 + isUpgrading = NO;
  96 + [_buttonPrepare setEnabled:NO];
  97 + [_buttonStart setEnabled:NO];
  98 + [_buttonStart setTitle:@"START" forState:UIControlStateNormal];
  99 + } else if ([name isEqualToString:kMsgOTAStatus]){
  100 + NSInteger state = ((NSNumber *)[dict objectForKey:@"status"]).integerValue;
  101 +
  102 + NSString *msg = @"UNKNOWN";
  103 + switch (state) {
  104 + case STATE_IDLE:
  105 + msg = @"IDLE";
  106 + break;
  107 +
  108 + case STATE_PREPARING:
  109 + msg = @"PREPARING";
  110 + break;
  111 +
  112 + case STATE_PREPARED:
  113 + msg = @"PREPARED";
  114 + break;
  115 +
  116 + case STATE_TRANSFERRING:
  117 + msg = @"TRANSFERRING";
  118 + beginTime = [NSDate date];
  119 + NSLog(@"nick_time:%@", beginTime);
  120 + break;
  121 +
  122 + case STATE_TRANSFERRED:
  123 + msg = @"TRANSFERRED";
  124 + isUpgrading = NO;
  125 + [_buttonStart setEnabled:YES];
  126 + [_buttonStart setTitle:@"START" forState:UIControlStateNormal];
  127 +
  128 + [self.otaManager confirmUpdateAndReboot];
  129 + break;
  130 +
  131 + default:
  132 + msg = @"UNKNOWN";
  133 + isUpgrading = NO;
  134 + [_buttonStart setEnabled:YES];
  135 + [_buttonStart setTitle:@"START" forState:UIControlStateNormal];
  136 + break;
  137 + }
  138 +
  139 + NSLog(@"onStatus: %ld %@", state, msg);
  140 + _labelBluetoothState.text = msg;
  141 + } else if ([name isEqualToString:kMsgAudioPackageRecevied]) {
  142 + NSInteger psn = ((NSNumber *)[dict objectForKey:@"psn"]).integerValue;
  143 + NSData *data = [dict objectForKey:@"data"];
  144 + NSLog(@"nick_speed");
  145 +
  146 + } else if ([name isEqualToString:kMsgRemoteStatusRecevied]) {
  147 + RemoteStatus *status = [dict objectForKey:@"status"];
  148 +
  149 + [_buttonStart setEnabled:YES];
  150 + } else if ([name isEqualToString:kMsgAudioSpeedRecevied]) {
  151 + NSInteger writeByte = ((NSNumber *)[dict objectForKey:@"speed"]).integerValue;
  152 + NSDate *now = [[NSDate alloc] init];
  153 + now = [NSDate date];
  154 + NSDateComponents *compt = [[NSCalendar currentCalendar] components:NSCalendarUnitMinute| NSCalendarUnitSecond fromDate:beginTime toDate:now options:0];
  155 + int min = compt.minute;
  156 + int sec = compt.second;
  157 + int time = min * 60 + sec;
  158 + double speed = writeByte / time / 1000;
  159 + NSLog(@"nick_now:%d", sec);
  160 + }
  161 +}
  162 +
  163 +- (NSString *)convertDataToHexStr:(NSData *)data {
  164 + if (!data || [data length] == 0) {
  165 + return @"";
  166 + }
  167 +
  168 + NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
  169 + [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
  170 + unsigned char *dataBytes = (unsigned char*)bytes;
  171 + for (NSInteger i = 0; i < byteRange.length; i++) {
  172 + NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
  173 + if ([hexStr length] == 2) {
  174 + [string appendString:hexStr];
  175 + } else {
  176 + [string appendFormat:@"0%@", hexStr];
  177 + }
  178 + }
  179 + }];
  180 +
  181 + return string;
19 182 }
20 183  
21   -/*
22   -#pragma mark - Navigation
  184 +- (void)showTextHUD:(NSString *) text {
  185 + dispatch_async(dispatch_get_main_queue(), ^{
  186 + // ๅฟซ้€Ÿๆ˜พ็คบไธ€ไธชๆ็คบไฟกๆฏ
  187 + MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  188 + hud.label.text = text;
  189 +
  190 + hud.label.textColor = [UIColor whiteColor];
  191 + hud.label.font = [UIFont systemFontOfSize:17.0];
  192 + hud.userInteractionEnabled = NO;
  193 +
  194 + hud.mode = MBProgressHUDModeCustomView;
  195 + hud.removeFromSuperViewOnHide = YES;
  196 + [hud hideAnimated:YES afterDelay:1.5];
  197 + });
  198 +}
  199 +
  200 +- (IBAction)didButtonStartClicked:(UIButton *)sender {
  201 + if (isUpgrading) {
  202 + [self showTextHUD:@"ๆญฃๅœจๅ‡็บงไธญ๏ผŒ่ฏท็จๅ€™๏ผ"];
  203 + return;
  204 + }
  205 + [self.otaManager upgrade];
  206 + isUpgrading = YES;
  207 + [_buttonStart setEnabled:NO];
  208 + [_buttonStart setTitle:@"UPGRADING" forState:UIControlStateNormal];
  209 +}
23 210  
24   -// In a storyboard-based application, you will often want to do a little preparation before navigation
25   -- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
26   - // Get the new view controller using [segue destinationViewController].
27   - // Pass the selected object to the new view controller.
  211 +- (IBAction)didButtonPrepareClicked:(UIButton *)sender {
  212 + if (isUpgrading) {
  213 + [self showTextHUD:@"ๆญฃๅœจๅ‡็บงไธญ๏ผŒ่ฏท็จๅ€™๏ผ"];
  214 + return;
  215 + }
  216 +
  217 +
  218 + NSLog(@"OTA path: %@", self.path);
  219 +
  220 + isUpgrading = NO;
  221 + [self.otaManager setOTAFile:self.path];
  222 +
  223 + NSString *version = [self.otaManager getOTAVersion];
  224 + [_labelOTAVersion setText:version];
  225 +
  226 + [self.otaManager prepare];
28 227 }
29   -*/
  228 +
30 229  
31 230 @end
... ...
HDFwear/OTA/OTAUpdateViewController.xib
1   -<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2   -<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
  3 + <device id="retina6_12" orientation="portrait" appearance="light"/>
3 4 <dependencies>
4   - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/>
  5 + <deployment identifier="iOS"/>
  6 + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
5 7 <capability name="Safe area layout guides" minToolsVersion="9.0"/>
  8 + <capability name="System colors in document resources" minToolsVersion="11.0"/>
6 9 <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7 10 </dependencies>
8 11 <objects>
9 12 <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="OTAUpdateViewController">
10 13 <connections>
  14 + <outlet property="buttonPrepare" destination="q1k-bS-BOp" id="seQ-pd-uwM"/>
  15 + <outlet property="buttonStart" destination="8O9-5g-jvV" id="C9e-Qx-ZHm"/>
  16 + <outlet property="labelBluetoothState" destination="gaI-tX-Hzm" id="3aD-Yk-KOf"/>
  17 + <outlet property="labelOTAVersion" destination="tSQ-CV-3Op" id="mYJ-YX-3hZ"/>
11 18 <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
12 19 </connections>
13 20 </placeholder>
14 21 <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
15 22 <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
16   - <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
  23 + <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
17 24 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18   - <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
  25 + <subviews>
  26 + <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qCx-Ed-671">
  27 + <rect key="frame" x="103" y="173" width="223" height="163.33333333333337"/>
  28 + <subviews>
  29 + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="OTA:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a9d-Ue-HmF">
  30 + <rect key="frame" x="4" y="0.0" width="97.333333333333329" height="19.333333333333332"/>
  31 + <constraints>
  32 + <constraint firstAttribute="width" constant="97.333333333333329" id="hbt-9j-g1E"/>
  33 + </constraints>
  34 + <fontDescription key="fontDescription" type="system" pointSize="16"/>
  35 + <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
  36 + <nil key="highlightedColor"/>
  37 + </label>
  38 + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d7B-CN-N5d">
  39 + <rect key="frame" x="20.000000000000004" y="21.333333333333343" width="55.333333333333343" height="18"/>
  40 + <fontDescription key="fontDescription" type="system" pointSize="15"/>
  41 + <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
  42 + <nil key="highlightedColor"/>
  43 + </label>
  44 + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tSQ-CV-3Op">
  45 + <rect key="frame" x="83.333333333333343" y="21.333333333333343" width="7" height="18"/>
  46 + <fontDescription key="fontDescription" type="system" pointSize="15"/>
  47 + <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
  48 + <nil key="highlightedColor"/>
  49 + </label>
  50 + </subviews>
  51 + <color key="backgroundColor" systemColor="systemGrayColor"/>
  52 + <constraints>
  53 + <constraint firstItem="tSQ-CV-3Op" firstAttribute="leading" secondItem="d7B-CN-N5d" secondAttribute="trailing" constant="8" id="1I7-xG-tJ5"/>
  54 + <constraint firstItem="a9d-Ue-HmF" firstAttribute="leading" secondItem="qCx-Ed-671" secondAttribute="leading" constant="4" id="KBW-4Z-D0j"/>
  55 + <constraint firstItem="d7B-CN-N5d" firstAttribute="leading" secondItem="qCx-Ed-671" secondAttribute="leading" constant="20" id="MBl-NG-OTE"/>
  56 + <constraint firstItem="a9d-Ue-HmF" firstAttribute="top" secondItem="qCx-Ed-671" secondAttribute="top" id="ZH5-g9-lyL"/>
  57 + <constraint firstItem="d7B-CN-N5d" firstAttribute="top" secondItem="a9d-Ue-HmF" secondAttribute="bottom" constant="2" id="k6W-2I-BCM"/>
  58 + <constraint firstItem="d7B-CN-N5d" firstAttribute="centerY" secondItem="tSQ-CV-3Op" secondAttribute="centerY" id="maq-88-mLj"/>
  59 + </constraints>
  60 + </view>
  61 + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q1k-bS-BOp">
  62 + <rect key="frame" x="79" y="375" width="65" height="30"/>
  63 + <state key="normal" title="PREPARE"/>
  64 + <connections>
  65 + <action selector="didButtonPrepareClicked:" destination="-1" eventType="touchUpInside" id="mxq-xo-Inf"/>
  66 + </connections>
  67 + </button>
  68 + <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8O9-5g-jvV">
  69 + <rect key="frame" x="244" y="375" width="47" height="30"/>
  70 + <state key="normal" title="START"/>
  71 + <connections>
  72 + <action selector="didButtonStartClicked:" destination="-1" eventType="touchUpInside" id="YcO-HE-rSb"/>
  73 + </connections>
  74 + </button>
  75 + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="N/A" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gaI-tX-Hzm">
  76 + <rect key="frame" x="163" y="104" width="26.333333333333343" height="20"/>
  77 + <fontDescription key="fontDescription" type="system" pointSize="16"/>
  78 + <color key="textColor" red="0.066666666669999999" green="0.41568627450000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
  79 + <nil key="highlightedColor"/>
  80 + </label>
  81 + </subviews>
19 82 <viewLayoutGuide key="safeArea" id="Q5M-cg-NOt"/>
  83 + <color key="backgroundColor" systemColor="systemBackgroundColor"/>
  84 + <constraints>
  85 + <constraint firstItem="q1k-bS-BOp" firstAttribute="top" secondItem="qCx-Ed-671" secondAttribute="bottom" constant="38.666666666666742" id="3pQ-Ta-3Ge"/>
  86 + <constraint firstItem="8O9-5g-jvV" firstAttribute="leading" secondItem="q1k-bS-BOp" secondAttribute="trailing" constant="100" id="5dl-hH-7lz"/>
  87 + <constraint firstItem="Q5M-cg-NOt" firstAttribute="trailing" secondItem="qCx-Ed-671" secondAttribute="trailing" constant="67" id="9F9-iF-5Oe"/>
  88 + <constraint firstItem="gaI-tX-Hzm" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" constant="163" id="L7m-N9-osX"/>
  89 + <constraint firstAttribute="bottom" secondItem="qCx-Ed-671" secondAttribute="bottom" constant="515.66666666666674" id="SFX-c3-CXV"/>
  90 + <constraint firstItem="qCx-Ed-671" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" constant="103" id="W3h-rc-0qz"/>
  91 + <constraint firstItem="q1k-bS-BOp" firstAttribute="baseline" secondItem="8O9-5g-jvV" secondAttribute="baseline" id="YW0-NU-whw"/>
  92 + <constraint firstItem="gaI-tX-Hzm" firstAttribute="top" secondItem="Q5M-cg-NOt" secondAttribute="top" constant="45" id="ePS-eY-NQ5"/>
  93 + <constraint firstItem="qCx-Ed-671" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="173" id="k45-uC-3Qh"/>
  94 + <constraint firstItem="q1k-bS-BOp" firstAttribute="leading" secondItem="Q5M-cg-NOt" secondAttribute="leading" constant="79" id="pH8-Bj-De1"/>
  95 + </constraints>
  96 + <point key="canvasLocation" x="138" y="20"/>
20 97 </view>
21 98 </objects>
  99 + <resources>
  100 + <systemColor name="systemBackgroundColor">
  101 + <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
  102 + </systemColor>
  103 + <systemColor name="systemGrayColor">
  104 + <color red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
  105 + </systemColor>
  106 + </resources>
22 107 </document>
... ...