2 // ARUpdaterViewController.m
5 // Created by Robert Ryll on 10-05-14.
6 // Copyright Playsoft 2010. All rights reserved.
9 #import "MenuUpdater.h"
10 #include <sys/socket.h>
11 #include <netinet/in.h>
12 #include <arpa/inet.h>
16 #define UPDATER_ASSERT(var, value) \
19 NSLog(@"oO ! "#var" is #%u (should be #%u)", (var), (value)); \
23 static NSArray *documentPath = nil;
25 static const NSString *stepKeys[] =
28 @"Checking/updating bootloader",
31 @"Restarting AR.Drone",
44 #define STATUS_LINE_NR 2
45 #define INDICATOR_S 10
47 #define IPAD_LINE_H 50
48 #define IPAD_INDICATOR_S 20
49 #define IPAD_OFFSET_X 50
51 #define OFFSET_Y (SCREEN_H - (STATUS_LINE_NR + NUMBER_OF_UPDATER_STEPS + 3) * LINE_H)
52 #define IMG_H (OFFSET_Y - LINE_H)
53 #define STATUS_Y (OFFSET_Y + LINE_H)
54 #define STEP_LINE_Y (STATUS_Y + LINE_H * STATUS_LINE_NR + LINE_H)
55 #define INDICATOR_M ((LINE_H - INDICATOR_S) / 2)
57 #define IPAD_OFFSET_Y ((IPAD_SCREEN_H - (STATUS_LINE_NR + NUMBER_OF_UPDATER_STEPS) * IPAD_LINE_H) / 2)
58 #define IPAD_IMG_H (IPAD_OFFSET_Y - IPAD_LINE_H)
59 #define IPAD_STATUS_Y (IPAD_OFFSET_Y + IPAD_LINE_H)
60 #define IPAD_STEP_LINE_Y (IPAD_STATUS_Y + IPAD_LINE_H * STATUS_LINE_NR + IPAD_LINE_H)
61 #define IPAD_INDICATOR_M ((IPAD_LINE_H - IPAD_INDICATOR_S) / 2)
65 #define VERSION_TXT @"version.txt"
66 #define REPAIR_VERSION_TXT @"repair_version.txt"
68 @implementation MenuUpdater
69 @synthesize firmwareVersion;
70 @synthesize firmwarePath;
71 @synthesize firmwareFileName;
72 @synthesize repairPath;
73 @synthesize repairFileName;
74 @synthesize repairVersion;
75 @synthesize bootldrPath;
76 @synthesize bootldrFileName;
77 @synthesize droneFirmwareVersion;
81 @synthesize repairFtp;
85 - (id) initWithController:(MenuController*)menuController
87 NSArray *targetArray = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UIDeviceFamily"];
91 for (int i = 0; i < [targetArray count]; i++)
93 NSNumber* num = (NSNumber*)[targetArray objectAtIndex:i];
95 [num getValue:&value];
98 compiledForIPad = YES;
104 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
109 using2xInterface = NO;
110 if ([UIScreen instancesRespondToSelector:@selector(scale)]) {
111 CGFloat scale = [[UIScreen mainScreen] scale];
113 using2xInterface = YES;
118 documentPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] copy];
120 if (usingIPad && compiledForIPad)
122 self = [super initWithNibName:@"MenuUpdater-iPad" bundle:nil];
126 self = [super initWithNibName:@"MenuUpdater" bundle:nil];
131 controller = menuController;
139 NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"Firmware" ofType:@"plist"];
140 NSDictionary *plistDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
141 self.firmwareFileName = [plistDict objectForKey:@"FirmwareFileName"];
142 self.repairFileName = [plistDict objectForKey:@"RepairFileName"];
143 self.bootldrFileName = [plistDict objectForKey:@"BootldrFileName"];
144 self.repairVersion = [plistDict objectForKey:@"RepairVersion"];
146 self.firmwarePath = [[NSBundle mainBundle] pathForResource:firmwareFileName ofType:@"plf"];
147 self.repairPath = [[NSBundle mainBundle] pathForResource:repairFileName ofType:@"bin"];
148 self.bootldrPath = [[NSBundle mainBundle] pathForResource:bootldrFileName ofType:@"bin"];
152 if(plf_get_header([self.firmwarePath cStringUsingEncoding:NSUTF8StringEncoding], &plf_header) != 0)
153 memset(&plf_header, 0, sizeof(plf_phdr));
155 self.firmwareVersion = [NSString stringWithFormat:@"%d.%d.%d", plf_header.p_ver, plf_header.p_edit, plf_header.p_ext];
158 firmwareVersionLabel.text = [NSString stringWithFormat:@"v%s_D", [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] cStringUsingEncoding:NSUTF8StringEncoding]];
160 firmwareVersionLabel.text = [NSString stringWithFormat:@"v%s", [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] cStringUsingEncoding:NSUTF8StringEncoding]];
163 if (usingIPad && compiledForIPad)
165 for (NSInteger i = 0; i < NUMBER_OF_UPDATER_STEPS ; i++)
167 stepLabel[i] = [UILabel new];
168 stepLabel[i].backgroundColor = [UIColor clearColor];
169 stepLabel[i].textColor = NORMAL_COLOR;
170 stepLabel[i].frame = CGRectMake(IPAD_LINE_H + IPAD_OFFSET_X, IPAD_STEP_LINE_Y + IPAD_LINE_H * i, IPAD_SCREEN_H - IPAD_LINE_H, IPAD_LINE_H);
171 stepLabel[i].text = NSLocalizedString((NSString *)stepKeys[i],);
172 [self.view addSubview:stepLabel[i]];
174 stepImageView[i] = [UIImageView new];
175 stepImageView[i].frame = CGRectMake(IPAD_OFFSET_X, IPAD_STEP_LINE_Y + i * IPAD_LINE_H, IPAD_LINE_H, IPAD_LINE_H);
176 stepImageView[i].image = [UIImage imageNamed:@"Empty-iPad.png"];
177 [self.view addSubview:stepImageView[i]];
179 if(i > UPDATER_STEP_CHECK)
181 stepLabel[i].hidden = YES;
182 stepImageView[i].hidden = YES;
188 for (NSInteger i = 0; i < NUMBER_OF_UPDATER_STEPS ; i++)
190 stepLabel[i] = [UILabel new];
191 stepLabel[i].backgroundColor = [UIColor clearColor];
192 stepLabel[i].textColor = NORMAL_COLOR;
193 stepLabel[i].frame = CGRectMake(LINE_H, STEP_LINE_Y + LINE_H * i, SCREEN_H - LINE_H, LINE_H);
194 stepLabel[i].text = NSLocalizedString((NSString *)stepKeys[i],);
195 [self.view addSubview:stepLabel[i]];
197 stepImageView[i] = [UIImageView new];
198 stepImageView[i].frame = CGRectMake(0, STEP_LINE_Y + i * LINE_H, LINE_H, LINE_H);
199 stepImageView[i].image = [UIImage imageNamed:@"Empty.png"];
200 [self.view addSubview:stepImageView[i]];
202 if(i > UPDATER_STEP_CHECK)
204 stepLabel[i].hidden = YES;
205 stepImageView[i].hidden = YES;
210 [okButton setTitle:NSLocalizedString(@"Ok",) forState:UIControlStateNormal];
211 [cancelButton setTitle:NSLocalizedString(@"Cancel",) forState:UIControlStateNormal];
213 stepIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
214 stepIndicator.hidesWhenStopped = YES;
215 [self.view addSubview:stepIndicator];
216 [stepIndicator release];
218 okButton.hidden = YES;
219 cancelButton.hidden = YES;
220 sendProgressView.hidden = YES;
222 self.fsm = [FiniteStateMachine fsmWithXML:[[NSBundle mainBundle] pathForResource:@"updater_fsm" ofType:@"xml"]];
224 fsm.currentState = UPDATER_STATE_WAITING_CONNECTION;
226 [self performSelector:@selector(checkFtp) withObject:nil afterDelay:5.];
229 - (void)didReceiveMemoryWarning
231 [super didReceiveMemoryWarning];
236 for (NSInteger i = 0 ; i < NUMBER_OF_UPDATER_STEPS ; i++)
238 [stepLabel[i] release];
239 [stepImageView[i] release];
242 self.firmwarePath = nil;
243 self.firmwareFileName = nil;
244 self.repairPath = nil;
245 self.repairFileName = nil;
246 self.repairVersion = nil;
247 self.bootldrPath = nil;
248 self.bootldrFileName = nil;
249 self.firmwareVersion = nil;
250 self.droneFirmwareVersion = nil;
254 self.repairFtp = nil;
258 [documentPath release];
263 // NSURLConnection needed to ping the ARDrone before creating the FTP.
264 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
266 NSLog(@"%@", response);
267 [connection release];
269 self.ftp = [ARDroneFTP anonymousUpdateFTPwithDelegate:self withDefaultCallback:nil];
271 if ([ftp keepConnexionAlive])
272 [fsm doAction:UPDATER_ACTION_SUCCESS];
274 [fsm doAction:UPDATER_ACTION_FAIL];
277 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
280 [connection release];
282 [fsm doAction:UPDATER_ACTION_FAIL];
285 - (void)executeCommandOut:(ARDRONE_COMMAND_OUT)commandId withParameter:(void*)parameter fromSender:(id)sender
289 case ARDRONE_COMMAND_RUN:
290 self.localIP = [NSString stringWithCString:(char*)parameter encoding:NSUTF8StringEncoding];
292 NSURLConnection *ping = [[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:FTP_ADDRESS, localIP, FTP_PORT, @""]]] delegate:self] retain];
304 if (fsm.currentState == UPDATER_STATE_WAITING_CONNECTION)
305 [fsm doAction:UPDATER_ACTION_FAIL];
308 // Updates the display with the FSM object.
309 - (void) updateStatusLabel
311 NSString *key = [(NSString *)fsm.currentObject stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
313 switch (fsm.currentState)
315 case UPDATER_STATE_NOT_CONNECTED:
316 case UPDATER_STATE_NOT_REPAIRED:
317 case UPDATER_STATE_NOT_UPDATED:
318 statusLabel.text = [NSString stringWithFormat:NSLocalizedString(key,), [[UIDevice currentDevice] model]];
320 case UPDATER_STATE_CHECK_VERSION:
322 switch ([firmwareVersion compare:droneFirmwareVersion options:NSNumericSearch])
324 case NSOrderedAscending:
325 statusLabel.text = [NSString stringWithFormat:NSLocalizedString(key,), droneFirmwareVersion, NSLocalizedString(@"Please update this application",), NSLocalizedString(@"Launching Application...",)];
328 statusLabel.text = [NSString stringWithFormat:NSLocalizedString(key,), droneFirmwareVersion, NSLocalizedString(@"AR.Drone up to date",), NSLocalizedString(@"Launching Application...",)];
331 statusLabel.text = [NSString stringWithFormat:NSLocalizedString(key,), droneFirmwareVersion, NSLocalizedString(@"Firmware update available",), NSLocalizedString(@"Do you want to update the AR.Drone ?",)];
337 statusLabel.text = NSLocalizedString(key,);
342 - (void) updateStepIndicator:(unsigned int)index
344 stepIndicator.frame = (usingIPad && compiledForIPad) ?
345 /* iPad */ CGRectMake(IPAD_OFFSET_X + IPAD_INDICATOR_M, IPAD_STEP_LINE_Y + IPAD_INDICATOR_M + IPAD_LINE_H * index, IPAD_INDICATOR_S, IPAD_INDICATOR_S) :
346 /* iPhone */ CGRectMake(INDICATOR_M, STEP_LINE_Y + INDICATOR_M + LINE_H * index, INDICATOR_S, INDICATOR_S);
349 - (void) updateStepImage:(unsigned int)index withImage:(unsigned int)image
353 case STEP_IMAGE_EMPTY:
354 stepImageView[index].image = [UIImage imageNamed:(usingIPad && compiledForIPad) ? @"Empty-iPad.png" : @"Empty.png"];
357 case STEP_IMAGE_PASS:
358 stepImageView[index].image = [UIImage imageNamed:(usingIPad && compiledForIPad) ? @"Pass-iPad.png" : @"Pass.png"];
361 case STEP_IMAGE_FAIL:
362 stepImageView[index].image = [UIImage imageNamed:(usingIPad && compiledForIPad) ? @"Fail-iPad.png" : @"Fail.png"];
365 case STEP_IMAGE_PROBLEM:
366 stepImageView[index].image = [UIImage imageNamed:(usingIPad && compiledForIPad) ? @"Problem-iPad.png" : @"Problem.png"];
375 * Waiting Connection State:
381 - (void)enterWaitingConnection:(id)_fsm
383 [self updateStatusLabel];
385 [self updateStepIndicator:UPDATER_STEP_CONNECTING];
386 [stepIndicator startAnimating];
390 - (void)quitWaitingConnection:(id)_fsm
392 [stepIndicator stopAnimating];
396 * Not Connected State:
402 - (void)enterNotConnected:(id)_fsm
404 [self updateStatusLabel];
406 [self updateStepImage:UPDATER_STEP_CONNECTING withImage:STEP_IMAGE_FAIL];
417 struct sockaddr_in address;
419 socket_desc=socket(AF_INET,SOCK_STREAM,0);
424 printf("Socket created (ip: %s)\n", [localIP cStringUsingEncoding:NSUTF8StringEncoding]);
426 /* type of socket created in socket() */
427 address.sin_family = AF_INET;
428 address.sin_addr.s_addr = inet_addr([localIP cStringUsingEncoding:NSUTF8StringEncoding]);
430 /* TELNET_PORT is the port to use for connections */
431 address.sin_port = htons(TELNET_PORT);
432 /* connect the socket to the port specified above */
433 connect(socket_desc, (struct sockaddr *)&address, sizeof(struct sockaddr));
435 // Change access right to XR
436 sprintf(buffer, "cd `find /data -name \"repair\" -exec dirname {} \\;` && chmod 755 repair\n");
437 size = send(socket_desc, buffer, strlen(buffer), 0);
438 if(size != strlen(buffer))
440 printf("Cannot change access right ...\n");
444 // execute repair binary file
445 sprintf(buffer, "cd `find /data -name \"repair\" -exec dirname {} \\;` && ./repair\n");
446 size = send(socket_desc, buffer, strlen(buffer), 0);
447 if(size != strlen(buffer))
449 printf("Cannot execute binary to repair AR.Drone ...\n");
457 perror("Can't create socket");
464 // Called back when repair is needed:
465 - (void)repairFileCallback:(ARDroneFTPCallbackArg *)arg
467 static unsigned int count = 0;
469 UPDATER_ASSERT(fsm.currentState, UPDATER_STATE_REPAIR)
470 UPDATER_ASSERT(arg.operation, FTP_SEND)
472 if (ARDFTP_FAILED(arg.status))
476 if (MAX_RETRIES == count)
478 [fsm doAction:UPDATER_ACTION_FAIL];
482 [repairFtp sendLocalFile:repairPath toDistantFile:repairFileName withResume:YES withCallback:@selector(repairFileCallback:)];
487 if (!ARDFTP_SUCCEEDED(arg.status))
490 NSLog(@"'repair' sent");
491 self.repairFtp = nil;
494 [fsm doAction:UPDATER_ACTION_SUCCESS];
496 [fsm doAction:UPDATER_ACTION_FAIL];
499 // Called back when bootldr is needed:
500 - (void)bootldrFileCallback:(ARDroneFTPCallbackArg *)arg
502 static unsigned int count = 0;
504 UPDATER_ASSERT(fsm.currentState, UPDATER_STATE_REPAIR)
505 UPDATER_ASSERT(arg.operation, FTP_SEND)
507 if (ARDFTP_FAILED(arg.status))
511 if (MAX_RETRIES == count)
513 [fsm doAction:UPDATER_ACTION_FAIL];
517 [repairFtp sendLocalFile:bootldrPath toDistantFile:[NSString stringWithFormat:@"%@.bin", bootldrFilename] withResume:YES withCallback:@selector(bootldrFileCallback:)];
522 if (!ARDFTP_SUCCEEDED(arg.status))
525 NSLog(@"'bootldr.bin' sent");
526 [repairFtp sendLocalFile:repairPath toDistantFile:repairFileName withResume:NO withCallback:@selector(repairFileCallback:)];
529 // Checks drone version:
530 - (void)checkRepairVersion
532 switch ([droneFirmwareVersion compare:repairVersion options:NSNumericSearch])
534 case NSOrderedAscending:
535 self.repairFtp = [ARDroneFTP anonymousStandardFTPwithDelegate:self withDefaultCallback:nil];
537 [repairFtp sendLocalFile:bootldrPath toDistantFile:[NSString stringWithFormat:@"%@.bin", bootldrFilename] withResume:NO withCallback:@selector(bootldrFileCallback:)];
542 [fsm doAction:UPDATER_ACTION_SUCCESS];
547 // Called back when drone version is found:
548 - (void)repairVersionTxtCallback:(ARDroneFTPCallbackArg *)arg
550 static unsigned int count = 0;
552 UPDATER_ASSERT(fsm.currentState, UPDATER_STATE_REPAIR)
553 UPDATER_ASSERT(arg.operation, FTP_GET)
555 NSString *path = [NSString stringWithFormat:@"%@/%@", documentPath, REPAIR_VERSION_TXT];
557 if (ARDFTP_FAILED(arg.status))
561 if (MAX_RETRIES == count)
563 [fsm doAction:UPDATER_ACTION_FAIL];
567 [ftp getDistantFile:VERSION_TXT toLocalFile:path withResume:YES withCallback:@selector(repairVersionTxtCallback:)];
571 if (!ARDFTP_SUCCEEDED(arg.status))
574 NSError *error = nil;
575 self.droneFirmwareVersion = [[NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:&error] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
577 [self performSelectorOnMainThread:@selector(checkRepairVersion) withObject:nil waitUntilDone:NO];
581 - (void)enterRepair:(id)_fsm
583 [self updateStatusLabel];
585 [self updateStepIndicator:UPDATER_STEP_REPAIR];
586 [stepIndicator startAnimating];
588 [self updateStepImage:UPDATER_STEP_CONNECTING withImage:STEP_IMAGE_PASS];
590 NSString *path = [NSString stringWithFormat:@"%@/%@", documentPath, REPAIR_VERSION_TXT];
592 NSError *error = nil;
594 if ([[NSFileManager defaultManager] fileExistsAtPath:path])
595 [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
597 [ftp getDistantFile:VERSION_TXT toLocalFile:path withResume:NO withCallback:@selector(repairVersionTxtCallback:)];
601 - (void)quitRepair:(id)_fsm
603 [stepIndicator stopAnimating];
612 - (void)enterNotRepaired:(id)_fsm
614 [self updateStatusLabel];
616 [self updateStepImage:UPDATER_STEP_REPAIR withImage:STEP_IMAGE_FAIL];
620 * Check Version State:
625 - (void)enterCheckVersion:(id)_fsm
627 [self updateStatusLabel];
629 [self updateStepIndicator:UPDATER_STEP_CHECK];
630 [stepIndicator startAnimating];
632 [self updateStepImage:UPDATER_STEP_REPAIR withImage:STEP_IMAGE_PASS];
634 switch ([firmwareVersion compare:droneFirmwareVersion options:NSNumericSearch])
636 case NSOrderedAscending:
637 [fsm doAction:UPDATER_ACTION_ASK_FOR_FREEFLIGHT_UPDATE];
640 [fsm doAction:UPDATER_ACTION_SUCCESS];
643 okButton.hidden = NO;
644 cancelButton.hidden = NO;
650 - (void)quitCheckVersion:(id)_fsm
652 [stepIndicator stopAnimating];
654 okButton.hidden = YES;
655 cancelButton.hidden = YES;
659 * Update Freeflight State:
664 - (void)enterUpdateFreeflight:(id)_fsm
666 [self updateStepImage:UPDATER_STEP_CHECK withImage:STEP_IMAGE_PROBLEM];
667 [self performSelector:@selector(cancelAction:) withObject:cancelButton afterDelay:TIME_BEFORE_LAUNCH];
671 * Launch Freeflight State:
676 - (void)enterLaunchFreeflight:(id)_fsm
678 for (unsigned int i = 0 ; i < NUMBER_OF_UPDATER_STEPS ; i++)
679 [self updateStepImage:i withImage:STEP_IMAGE_PASS];
681 [self performSelector:@selector(cancelAction:) withObject:cancelButton afterDelay:TIME_BEFORE_LAUNCH];
685 * Update Firmware State:
689 // Send Progress View can only be updated on Main Thread
690 - (void)updateSendProgressView:(NSNumber *)progress
692 sendProgressView.progress = [progress floatValue];
695 // Called back when file sending is in progress.
696 - (void)sendFileCallback:(ARDroneFTPCallbackArg *)arg
698 static unsigned int count = 0;
700 UPDATER_ASSERT(fsm.currentState, UPDATER_STATE_UPDATE_FIRMWARE)
701 UPDATER_ASSERT(arg.operation, FTP_SEND)
703 if (ARDFTP_FAILED(arg.status))
707 if (MAX_RETRIES == count)
709 [fsm doAction:UPDATER_ACTION_FAIL];
713 [ftp sendLocalFile:firmwarePath toDistantFile:[NSString stringWithFormat:@"%@.plf", firmwareFileName] withResume:YES withCallback:@selector(sendFileCallback:)];
717 [self performSelectorOnMainThread:@selector(updateSendProgressView:) withObject:[NSNumber numberWithFloat:arg.progress/100.f] waitUntilDone:NO];
719 if (!ARDFTP_SUCCEEDED(arg.status))
722 NSLog(@"'firmware.plf' sent");
723 [fsm doAction:UPDATER_ACTION_SUCCESS];
727 - (void)enterUpdateFirmware:(id)_fsm
729 [self updateStatusLabel];
731 [self updateStepImage:UPDATER_STEP_CHECK withImage:STEP_IMAGE_PASS];
733 for (unsigned int i = 0 ; i < NUMBER_OF_UPDATER_STEPS ; i++)
735 stepLabel[i].hidden = NO;
736 stepImageView[i].hidden = NO;
739 [self updateStepIndicator:UPDATER_STEP_SEND];
740 [stepIndicator startAnimating];
742 sendProgressView.hidden = NO;
743 sendProgressView.progress = 0.f;
745 [ftp sendLocalFile:firmwarePath toDistantFile:[NSString stringWithFormat:@"%@.plf", firmwareFileName] withResume:NO withCallback:@selector(sendFileCallback:)];
749 - (void)quitUpdateFirmware:(id)_fsm
751 [stepIndicator stopAnimating];
753 sendProgressView.hidden = YES;
762 - (void)enterNotUpdated:(id)_fsm
764 [self updateStatusLabel];
766 [self updateStepImage:UPDATER_STEP_SEND withImage:STEP_IMAGE_FAIL];
770 * Restart Drone State:
774 // Checks if the drone is powered off
775 - (void)checkDroneRestarted
777 UPDATER_ASSERT(fsm.currentState, UPDATER_STATE_RESTART_DRONE)
779 if ([ftp keepConnexionAlive])
780 [self performSelector:@selector(checkDroneRestarted) withObject:nil afterDelay:1];
782 [fsm doAction:UPDATER_ACTION_SUCCESS];
786 - (void)enterRestartDrone:(id)_fsm
788 [self updateStatusLabel];
790 [self updateStepImage:UPDATER_STEP_SEND withImage:STEP_IMAGE_PASS];
792 [self updateStepIndicator:UPDATER_STEP_RESTART];
793 [stepIndicator startAnimating];
795 [self performSelectorOnMainThread:@selector(checkDroneRestarted) withObject:nil waitUntilDone:NO];
799 - (void)quitRestartDrone:(id)_fsm
801 [stepIndicator stopAnimating];
805 * Installing Firmware State:
810 - (void)enterInstallingFirmware:(id)_fsm
812 [self updateStatusLabel];
814 [self updateStepImage:UPDATER_STEP_RESTART withImage:STEP_IMAGE_PASS];
816 [self updateStepIndicator:UPDATER_STEP_INSTALL];
817 [stepIndicator startAnimating];
821 - (IBAction)okAction:(id)sender
823 [fsm doAction:UPDATER_ACTION_FAIL];
826 - (IBAction)cancelAction:(id)sender
828 [controller doAction:MENU_ACTION_NEXT];