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

RawEEGRaw_EEG_PlotServoTab_Interfacepyramid
Last change on this file since daf54d5 was daf54d5, checked in by Steve Castellotti <sc@…>, 7 years ago
  • Connect button added to interface
  • Variable volume level fixed
  • Enhanced display log output
  • Property mode set to 100644
File size: 10.8 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- (void)didReceiveMemoryWarning
46{
47   [super didReceiveMemoryWarning];
48   // Dispose of any resources that can be recreated.
49   [self stopDemo];
50}
51
52// Start the data stream from the bluetooth headset if it's attached, and reset the display output
53- (void) appForegrounded
54{
55   //   EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
56   //   if (accessory != nil) {
57   //      [self logMessage:[NSString stringWithFormat:@"App opened with %@ connected. Starting data stream", accessory.name]];
58   //      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
59   //   }
60   [self resetViews]; // just in case it didn't happen on close
61}
62
63// Set all levels back to zero and clear the log
64- (void) resetViews
65{
66   [self resetOutputToZero];
67   self.log.text = @"";
68}
69
70- (void) resetOutputToZero
71{
72   [self showSignalStrength:[NSNumber numberWithInt:200]];
73   [self setAttentionLevel:[NSNumber numberWithInt:0]];
74   [self setMeditationLevel:[NSNumber numberWithInt:0]];
75   [self showPowerLevel];
76}
77
78- (void) appClosed
79{
80   [self stopDemo];
81   [self resetViews];
82   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
83      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
84   }
85}
86
87- (void) prepareAudio
88{
89   NSURL *audioFilePath = [[NSBundle mainBundle] URLForResource:AUDIO_FILE_NAME withExtension:nil];
90   
91   NSError *error;
92   audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:audioFilePath error:&error];
93   if (!audioPlayer) {
94      [self logMessage:[NSString stringWithFormat:@"Failed to initialize audio player: %@", [error debugDescription]]];
95   } else {
96      [audioPlayer prepareToPlay];
97   }
98}
99
100- (void) logMessage:(NSString *) str
101{
102   log.text = [[NSString alloc] initWithFormat:@"%@\n%@", str, log.text];
103}
104
105- (void) logDataReceived:(NSDictionary *) data
106{
107   NSNumber *sig = [data valueForKey:POOR_SIGNAL_KEY];
108   if (sig != NULL) {
109      int val = (200 - [sig intValue]) / 2;
110      [self logMessage:[NSString stringWithFormat:@"Signal strength: %u%%, Attention: %@%%, Meditation: %@%%",
111                        val, [data valueForKey:ATTENTION_KEY], [data valueForKey:MEDITATION_KEY]]];
112   }
113   
114}
115
116#pragma mark - TGAccessoryDelegate methods
117
118- (void)dataReceived:(NSDictionary *)data {
119   [self performSelectorOnMainThread:@selector(updatedSignalReceived:)
120                          withObject:data
121                       waitUntilDone:NO];
122}
123
124- (void) updatedSignalReceived:(id) data
125{
126   if (LOGGING) {
127      [self logDataReceived: data];
128   }
129   [self showSignalStrength:(NSNumber *)[data valueForKey:POOR_SIGNAL_KEY]];
130   [self setAttentionLevel:(NSNumber *)[data valueForKey:ATTENTION_KEY]];
131   [self setMeditationLevel:(NSNumber *)[data valueForKey:MEDITATION_KEY]];
132   [self showPowerLevel];
133   [self playAudio];
134}
135
136// The headset was switched on, start the data stream
137- (void)accessoryDidConnect:(EAAccessory *)accessory {
138   [self logMessage:[NSString stringWithFormat:@"%@ connected to this device", [accessory name]]];
139   if ([[TGAccessoryManager sharedTGAccessoryManager] accessory] != nil) {
140      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
141   }
142}
143
144// The headset was switched off (or the Bluetooth signal was dropped).
145// Reset the outputs back to zero
146- (void)accessoryDidDisconnect {
147   [self logMessage:@"Accessory was disconnected"];
148   [self resetOutputToZero];
149}
150
151#pragma mark - display calculation and update methods
152
153// when the user adjusts one of the 2 threshold sliders we recalculate the power values at each of the 101 possible levels
154- (void) calculatePowerValues
155{
156   float threshold = self.attentionThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
157   for (int i = 0; i < 101; i++) {
158      attentionPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
159   }
160   threshold = self.meditationThreshold.value; // this is the user-selected minimum threshold - between 0 and 1
161   for (int i = 0; i < 101; i++) {
162      meditationPower[i] = [self calculatePowerAt:(float)i/100 withThreshold:threshold];
163   }
164}
165
166// Convert a value (attention or meditation) into the corresponding power output (which is the volume level)
167// given the threshold is set at specified value.
168// The threshold is the value set by the user as the minimum value (attention or meditation) to be reached
169// before the helicopter is given the signal to fly.
170// If the value if below the threshold, the value will be zero (threshold not yet met).
171// Otherwise the returned power value will be between 0 and 1.
172- (float) calculatePowerAt:(float)value withThreshold:(float)threshold
173{
174   if (value < threshold) { // threshold not met
175      return 0;
176   }
177   // e.g. if the threshold is 0.55 and the current value is 0.7:
178   // 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)
179   return (value - threshold) / (1 - threshold);
180}
181
182- (void) showSignalStrength:(NSNumber *) value
183{
184   if (value != nil) {
185      self.signal.progress = (200.0 - [value intValue]) / 200.0;
186   }
187}
188
189- (void) setAttentionLevel:(NSNumber *) value
190{
191   if (value != nil) {
192      currentAttentionLevel = [value intValue];
193      self.attention.progress = (float)currentAttentionLevel / 100;
194   }
195}
196
197- (void) setMeditationLevel:(NSNumber *) value
198{
199   if (value != nil) {
200      currentMeditationLevel = [value intValue];
201      self.meditation.progress = (float)currentMeditationLevel / 100;
202   }
203}
204
205// updates the power output progress bar
206- (void) showPowerLevel
207{
208   self.power.progress = [self currentPowerLevel];
209}
210
211// calculate the total power level to output through the headphones, a value between zero and 1.
212// this is the attention signal level and meditation signal level added together
213- (float) currentPowerLevel
214{
215   float powerLevel = attentionPower[currentAttentionLevel] + meditationPower[currentMeditationLevel];
216   if (powerLevel > 1) {
217      return 1.0;
218   }
219   return powerLevel;
220}
221
222- (void) playAudio
223{
224   //   audioPlayer.volume = [self currentPowerLevel];
225   if ([self currentPowerLevel] > 0) {
226      audioPlayer.volume = 100;
227      [audioPlayer play];
228   } else {
229      [audioPlayer stop];
230   }
231}
232
233#pragma mark - button press events
234
235// allow simulation of values being generated by a headset and being received on the phone
236- (IBAction) demoButtonPressed:(id) sender
237{
238   if (demoRunning) {
239      [self stopDemo];
240   } else {
241      [self startDemo];
242   }
243}
244
245- (void) stopDemo
246{
247   demoRunning = NO;
248   [self resetOutputToZero];
249   [demoButton setTitle:@"Demo" forState:UIControlStateNormal];
250   if (audioPlayer != NULL && [audioPlayer isPlaying]) {
251      [audioPlayer stop];
252   }
253   [self logMessage:@"Demo stopped"];
254}
255
256- (void) startDemo
257{
258   demoRunning = YES;
259   [self logMessage:@"Running demo"];
260   [demoButton setTitle:@"Stop" forState:UIControlStateNormal];
261   [self performSelectorInBackground:@selector(generateDemoData) withObject:nil];
262}
263
264// generate some data in a background thread and invoke the methods the headset would invoke with the data
265// for testing without a headset.
266- (void) generateDemoData
267{
268   while (demoRunning) {
269      for (int i = 0; i < 100; i++) {
270         if (!demoRunning) {
271            break; // break out if we stop the demo
272         }
273         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
274                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
275                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
276                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
277                                 nil];
278         [self dataReceived:values];
279         [NSThread sleepForTimeInterval:0.1];
280      }
281     
282      for (int i = 100; i > 0; i--) {
283         if (!demoRunning) {
284            break; // break out if we stop the demo
285         }
286         NSDictionary *values = [NSDictionary dictionaryWithObjectsAndKeys:
287                                 [NSNumber numberWithInt:i], ATTENTION_KEY,
288                                 [NSNumber numberWithInt:i * 0.8], MEDITATION_KEY,
289                                 [NSNumber numberWithInt:0], POOR_SIGNAL_KEY,
290                                 nil];
291         [self dataReceived:values];
292         [NSThread sleepForTimeInterval:0.1];
293      }
294     
295   }
296}
297
298- (void) startProcessing
299{
300   processingDevice = YES;
301   [self logMessage:@"Connecting to EEG Headset"];
302   [connectButton setTitle:@"Disconnect" forState:UIControlStateNormal];
303   
304   EAAccessory *accessory = [[TGAccessoryManager sharedTGAccessoryManager] accessory];
305   if (accessory != nil) {
306      [self logMessage:[NSString stringWithFormat:@"EEG device %@ connected", accessory.name]];
307      [[TGAccessoryManager sharedTGAccessoryManager] startStream];
308   }
309   
310}
311
312- (void) stopProcessing
313{
314   processingDevice = NO;
315   [self logMessage:@"Disconnecting from EEG Headset"];
316   if ([[TGAccessoryManager sharedTGAccessoryManager] connected]) {
317      [[TGAccessoryManager sharedTGAccessoryManager] stopStream];
318   }
319   [connectButton setTitle:@"Connect" forState:UIControlStateNormal];
320   
321   [self resetOutputToZero];
322   [audioPlayer stop];
323   
324}
325
326- (IBAction) connectButtonPressed:(id) sender {
327   
328   if (processingDevice) {
329      [self stopProcessing];
330   } else {
331      [self startProcessing];
332   }
333   
334}
335@end
Note: See TracBrowser for help on using the repository browser.