source: orbit/iOS/Orbit/Orbit/ViewController.m @ bb28823

RawEEGRaw_EEG_PlotServoTab_Interfacepyramid
Last change on this file since bb28823 was bb28823, checked in by Steve Castellotti <sc@…>, 9 years ago
  • volume setting fixed
  • instructions text updated
  • Property mode set to 100644
File size: 11.0 KB
Line 
1//
2//  ViewController.m
3//  Orbit
4//
5//  Created by Jonathon Horsman on 17/12/2012.
6//  Copyright (c) 2012 Puzzlebox Productions, LLC. All rights reserved.
7//
8
9#import "ViewController.h"
10#import <AVFoundation/AVFoundation.h>
11
12//#define LOGGING true // set to false to stop each received signal being logged
13#define LOGGING false // set to false to stop each received signal being logged
14
15#define AUDIO_FILE_NAME @"throttle_hover_ios.wav" // @"iOS_noflip.wav" //
16//#define AUDIO_FILE_NAME @"iOS_noflip.wav"
17#define POOR_SIGNAL_KEY @"poorSignal"
18#define ATTENTION_KEY @"eSenseAttention"
19#define MEDITATION_KEY @"eSenseMeditation"
20
21@implementation ViewController {
22   // these 2 arrays hold the values of the thrust which should be applied to the helicopter
23   // (by way of volume level through the headphones) at each level of attention and meditation.
24   // this could be calculated on the fly, but because it happens ~20 times per second, the
25   // better option is to store all the values in arrays, and recalculate when the sliders are changed.
26   float attentionPower[101];
27   float meditationPower[101];
28   
29   int currentAttentionLevel, currentMeditationLevel; // the latest readings from the headset
30   BOOL processingDevice;
31   BOOL demoRunning;
32}
33
34@synthesize log, attention, meditation, signal, power, attentionThreshold, meditationThreshold, connectButton, demoButton;
35
36- (void)viewDidLoad
37{
38   [super viewDidLoad];
39   [self prepareAudio];
40   [self.attentionThreshold addTarget:self action:@selector(calculatePowerValues) forControlEvents:UIControlEventValueChanged];
41   [self.meditationThreshold addTarget:self action:@selector(calculatePowerValues) forControlEvents:UIControlEventValueChanged];
42   [self calculatePowerValues];
43   
44}
45
46- (void)didReceiveMemoryWarning
47{
48   [super didReceiveMemoryWarning];
49   // Dispose of any resources that can be recreated.
50   [self stopDemo];
51}
52
53// Start the data stream from the bluetooth headset if it's attached, and reset the display output
54- (void) appForegrounded
55{
56   [self resetViews]; // just in case it didn't happen on close
57}
58
59// Set all levels back to zero and clear the log
60- (void) resetViews
61{
62   [self resetOutputToZero];
63   self.log.text = @"";
64   [self printInstructions];
65   
66}
67
68- (void) printInstructions
69{
70   [self logMessage:@"\nWelcome to Puzzlebox Orbit!\n\nInstructions for Use:\n1. Pair with EEG headset under Bluetooth Settings\n2. Attach infrared dongle to headphone port\n3. Raise volume to maximum level\n4. Press Connect button below to begin processing\n5. When Attention or Meditation levels cross the indicated threshold, the helicopter will take off\n\nPlease visit http://orbit.puzzlebox.info for more details"];
71}
72
73- (void) resetOutputToZero
74{
75   [self showSignalStrength:[NSNumber numberWithInt:200]];
76   [self setAttentionLevel:[NSNumber numberWithInt:0]];
77   [self setMeditationLevel:[NSNumber numberWithInt:0]];
78   [self showPowerLevel];
79}
80
81- (void) appClosed
82{
83   [self stopDemo];
84   [self resetViews];
85   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
86      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
87   }
88}
89
90- (void) prepareAudio
91{
92   NSURL *audioFilePath = [[NSBundle mainBundle] URLForResource:AUDIO_FILE_NAME withExtension:nil];
93   
94   NSError *error;
95   audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFilePath error:&error];
96   if (!audioPlayer) {
97      [self logMessage:[NSString stringWithFormat:@"Failed to initialize audio player: %@", [error debugDescription]]];
98   } else {
99      [audioPlayer prepareToPlay];
100   }
101}
102
103- (void) logMessage:(NSString *) str
104{
105   log.text = [[NSString alloc] initWithFormat:@"%@\n%@", str, log.text];
106}
107
108- (void) logDataReceived:(NSDictionary *) data
109{
110   NSNumber *sig = [data valueForKey:POOR_SIGNAL_KEY];
111   if (sig != NULL) {
112      int val = (200 - [sig intValue]) / 2;
113      [self logMessage:[NSString stringWithFormat:@"Signal strength: %u%%, Attention: %@%%, Meditation: %@%%",
114                        val, [data valueForKey:ATTENTION_KEY], [data valueForKey:MEDITATION_KEY]]];
115   }
116   
117}
118
119#pragma mark - TGAccessoryDelegate methods
120
121- (void)dataReceived:(NSDictionary *)data {
122   [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
123                          withObject:data
124                       waitUntilDone:NO];
125}
126
127- (void) updatedSignalReceived:(id) data
128{
129   if (LOGGING) {
130      [self logDataReceived: data];
131   }
132   [self showSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
133   [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
134   [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];
135   [self showPowerLevel];
136   [self playAudio];
137}
138
139// The headset was switched on, start the data stream
140- (void)accessoryDidConnect:(EAAccessory *)accessory {
141   [self logMessage:[NSString stringWithFormat:@"%@ connected to this device", [accessory name]]];
142   if ([[TGAccessoryManager sharedTGAccessoryManager] accessory] != nil) {
143      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
144   }
145}
146
147// The headset was switched off (or the Bluetooth signal was dropped).
148// Reset the outputs back to zero
149- (void)accessoryDidDisconnect {
150   [self logMessage:@"Accessory was disconnected"];
151   [self resetOutputToZero];
152}
153
154#pragma mark - display calculation and update methods
155
156// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
157- (void) calculatePowerValues
158{
159   float threshold = self.attentionThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
160   for (int i = 0; i < 101; i++) {
161      attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
162   }
163   threshold = self.meditationThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
164   for (int i = 0; i < 101; i++) {
165      meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
166   }
167}
168
169// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
170// given the threshold is set at specified value.
171// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
172// before the helicopter is given the signal to fly.
173// If the value if below the threshold, the value will be zero (threshold not yet met).
174// Otherwise the returned power value will be between 0 and 1.
175- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
176{
177   if (value < threshold) { // threshold not met
178      return 0;
179   }
180   // e.g. if the threshold is 0.55 and the current value is 0.7:
181   // there is 0.45 remaining of the threshold slider, and the value is 0.15 past the threshold (0.7 - 0.55), which is 0.33 (0.15 / 0.45)
182   return (value - threshold) / (1 - threshold);
183}
184
185- (void) showSignalStrength:(NSNumber *) value
186{
187   if (value != nil) {
188      self.signal.progress = (200.0 - [value intValue]) / 200.0;
189   }
190}
191
192- (void) setAttentionLevel:(NSNumber *) value
193{
194   if (value != nil) {
195      currentAttentionLevel = [value intValue];
196      self.attention.progress = (float)currentAttentionLevel / 100;
197   }
198}
199
200- (void) setMeditationLevel:(NSNumber *) value
201{
202   if (value != nil) {
203      currentMeditationLevel = [value intValue];
204      self.meditation.progress = (float)currentMeditationLevel / 100;
205   }
206}
207
208// updates the power output progress bar
209- (void) showPowerLevel
210{
211   self.power.progress = [self currentPowerLevel];
212}
213
214// calculate the total power level to output through the headphones, a value between zero and 1.
215// this is the attention signal level and meditation signal level added together
216- (float) currentPowerLevel
217{
218   float powerLevel = attentionPower[currentAttentionLevel] + meditationPower[currentMeditationLevel];
219   if (powerLevel > 1) {
220      return 1.0;
221   }
222   return powerLevel;
223}
224
225- (void) playAudio
226{
227   //   audioPlayer.volume = [self currentPowerLevel];
228   if ([self currentPowerLevel] > 0) {
229      audioPlayer.volume = 1.0;
230      [audioPlayer play];
231   } else {
232      [audioPlayer stop];
233   }
234}
235
236#pragma mark - button press events
237
238// allow simulation of values being generated by a headset and being received on the phone
239- (IBAction) demoButtonPressed:(id) sender
240{
241   if (demoRunning) {
242      [self stopDemo];
243   } else {
244      [self startDemo];
245   }
246}
247
248- (void) stopDemo
249{
250   demoRunning = NO;
251   [self resetOutputToZero];
252   [demoButton setTitle:@"Demo" forState:UIControlStateNormal];
253   if (audioPlayer != NULL && [audioPlayer isPlaying]) {
254      [audioPlayer stop];
255   }
256   [self logMessage:@"Demo stopped"];
257}
258
259- (void) startDemo
260{
261   demoRunning = YES;
262   [self logMessage:@"Running demo"];
263   [demoButton setTitle:@"Stop" forState:UIControlStateNormal];
264   [self performSelectorInBackground:@selector(generateDemoData) withObject:nil];
265}
266
267// generate some data in a background thread and invoke the methods the headset would invoke with the data
268// for testing without a headset.
269- (void) generateDemoData
270{
271   while (demoRunning) {
272      for (int i = 0; i < 100; i++) {
273         if (!demoRunning) {
274            break; // break out if we stop the demo
275         }
276         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
277                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
278                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
279                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
280                                 nil];
281         [self dataReceived:values];
282         [NSThread sleepForTimeInterval:0.1];
283      }
284     
285      for (int i = 100; i > 0; i--) {
286         if (!demoRunning) {
287            break; // break out if we stop the demo
288         }
289         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
290                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
291                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
292                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
293                                 nil];
294         [self dataReceived:values];
295         [NSThread sleepForTimeInterval:0.1];
296      }
297     
298   }
299}
300
301- (void) startProcessing
302{
303   processingDevice = YES;
304   [self logMessage:@"Connecting to EEG Headset"];
305   [connectButton setTitle:@"Disconnect" forState:UIControlStateNormal];
306   
307   EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
308   if (accessory != nil) {
309      [self logMessage:[NSString stringWithFormat:@"EEG device %@ connected", accessory.name]];
310      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
311   }
312   
313}
314
315- (void) stopProcessing
316{
317   processingDevice = NO;
318   [self logMessage:@"Disconnecting from EEG Headset"];
319   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
320      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
321   }
322   [connectButton setTitle:@"Connect" forState:UIControlStateNormal];
323   
324   [self resetOutputToZero];
325   [audioPlayer stop];
326   
327}
328
329- (IBAction) connectButtonPressed:(id) sender {
330   
331   if (processingDevice) {
332      [self stopProcessing];
333   } else {
334      [self startProcessing];
335   }
336   
337}
338@end
Note: See TracBrowser for help on using the repository browser.