What is the Problem?
As the ocean’s surface temperature and CO2 emissions continue to increase due to climate change, we are losing one of the most biologically diverse habitats in the world: coral reefs; often referred to as the “rainforest of the seas.” Roughly 27% of coral reefs worldwide already considered damaged beyond repair, and two-thirds are under serious threat.1 Coral reefs are home to almost one-quarter of all ocean species (nearly 1 million species of fish, invertebrates and algae) and are essential for the daily survival of more than 30 million people through tourism, fishing, and food supply.
As the ocean’s surface temperature and CO2 emissions continue to increase due to climate change, we are losing one of the most biologically diverse habitats in the world: coral reefs; often referred to as the “rainforest of the seas.” Roughly 27% of coral reefs worldwide already considered damaged beyond repair, and two-thirds are under serious threat.1 Coral reefs are home to almost one-quarter of all ocean species (nearly 1 million species of fish, invertebrates and algae) and are essential for the daily survival of more than 30 million people through tourism, fishing, and food supply.
Coral reefs are a small yet integral part of the underwater ecosystem, covering roughly 108,000 square miles, or just 0.2% of the entire ocean floor. Reefs provide food and medicine, protect shores from storms and tsunamis, and are the source of income for many local communities. Their existence is extremely fragile: more than 80% of the worlds shallow reefs are severely overfished, land-based pollution and costal development have put 26% of reefs at risk, and the CO2 absorption decreases in pH levels have caused reduced calcification rates in reef-building and reef associated organisms.2
If the present rate of destruction continues, 60% of the worlds coral reefs will be destroyed by 2045. In order to make the necessary progress to save the reefs, it is essential that humans increase coral reef monitoring and data collection processes, which began just 20 years ago.3
What Will We Do About It?
We do it by making your iPhone into a dive computer. To do this we build a custom, water-proof, casing to fit your iPhone. We attach customized microcontrollers and sensors to that collect data such as pH, salinity, and oxygen saturation on your mobile device that is then uploaded to a database for researchers to use.
By putting measurement tools in the hands of divers, we could astronomically increase the rate of data collection and sharing near real time. As the oceans are intimately connected, the database can be used to compare reef trends across the world. The more data we give to conservationists and researchers on coral reefs, the more capable they will be to save them. By placing the instruments of data collection in the hands of everyday divers, we will encourage the public to be more conscious of the places they explore. We hope that by putting this responsibility into the hands of recreational divers, the responsibility will pervade the social conscience, and more people will be inspired to protect one of Earth’s most important ecosystems.
SeaDeepArduino.ino
C/C++/*
Copyright (c) 2012, 2013 RedBearLab
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//"services.h/spi.h/boards.h" is needed in every new project
#include <SPI.h>
#include <boards.h>
#include <ble_shield.h>
#include <services.h>
#include <Servo.h>
#define DIGITAL_OUT_PIN 4
#define DIGITAL_IN_PIN 5
#define PWM_PIN 6
#define SERVO_PIN 7
#define ANALOG_IN_PIN A5
#define ANALOG_IN_PIN_2 A4
Servo myservo;
void setup()
{
// Default pins set to 9 and 8 for REQN and RDYN
// Set your REQN and RDYN here before ble_begin() if you need
//ble_set_pins(3, 2);
// Init. and start BLE library.
ble_begin();
// Enable serial debug
Serial.begin(57600);
pinMode(DIGITAL_OUT_PIN, OUTPUT);
pinMode(DIGITAL_IN_PIN, INPUT);
// Default to internally pull high, change it if you need
digitalWrite(DIGITAL_IN_PIN, HIGH);
//digitalWrite(DIGITAL_IN_PIN, LOW);
myservo.attach(SERVO_PIN);
}
void loop()
{
static boolean analog_enabled = true;
static byte old_state = LOW;
// If data is ready
while(ble_available())
{
// read out command and data
byte data0 = ble_read();
byte data1 = ble_read();
byte data2 = ble_read();
analog_enabled = true;
if (data0 == 0x01) // Command is to control digital out pin
{
if (data1 == 0x01)
digitalWrite(DIGITAL_OUT_PIN, HIGH);
else
digitalWrite(DIGITAL_OUT_PIN, LOW);
}
// else if (data0 == 0xA0) // Command is to enable analog in reading
// {
// if (data1 == 0x01)
// analog_enabled = true;
// else
// analog_enabled = false;
// }
else if (data0 == 0x02) // Command is to control PWM pin
{
analogWrite(PWM_PIN, data1);
}
else if (data0 == 0x03) // Command is to control Servo pin
{
myservo.write(data1);
}
else if (data0 == 0x04)
{
analog_enabled = false;
myservo.write(0);
analogWrite(PWM_PIN, 0);
digitalWrite(DIGITAL_OUT_PIN, LOW);
}
}
analog_enabled = true;
if (analog_enabled) // if analog reading enabled
{
// Read and send out
int reading = analogRead(ANALOG_IN_PIN);
float voltage = reading * 5.0;
voltage /= 1024.0;
float temperatureC = (voltage - 0.5) *100;
float temperatureF = (temperatureC * 9/5) + 32;
uint16_t value = temperatureF;
//uint16_t value = analogRead(ANALOG_IN_PIN) * 5 / 1024;
ble_write(0x0B);
ble_write(value >> 8);
ble_write(value);
// value2
unsigned long fsrResistance;
unsigned long fsrConductance;
long fsrForce;
int fsrReading = analogRead(ANALOG_IN_PIN_2);
int fsrVoltage = map(fsrReading, 0, 1023, 0, 5000);
if (fsrVoltage == 0) {
Serial.println("No pressure");
fsrForce = 0;
}
else
{
fsrResistance = 5000 - fsrVoltage;
fsrResistance *= 10000;
fsrResistance /= fsrVoltage;
fsrConductance = 1000000;
fsrConductance /= fsrResistance;
if (fsrConductance <= 1000) {
fsrForce = fsrConductance / 80; //Force in Newtons
}
else {
fsrForce = fsrConductance - 1000;
fsrForce /= 30; //Force in Newtons
}
}
fsrForce *= 0.2248; // Force in pounds
long depth = fsrForce*3.46719; // h = P/(rho*g)
uint16_t value2 = depth;
ble_write(0x0C);
ble_write(value2 >> 8);
ble_write(value2);
delay(500);
}
// If digital in changes, report the state
if (digitalRead(DIGITAL_IN_PIN) != old_state)
{
old_state = digitalRead(DIGITAL_IN_PIN);
if (digitalRead(DIGITAL_IN_PIN) == HIGH)
{
ble_write(0x0A);
ble_write(0x01);
ble_write(0x00);
}
else
{
ble_write(0x0A);
ble_write(0x00);
ble_write(0x00);
}
}
if (!ble_connected())
{
analog_enabled = false;
digitalWrite(DIGITAL_OUT_PIN, LOW);
}
// Allow BLE Shield to send/receive data
ble_do_events();
}
//
// ViewController.h
// SeaDeep
//
// Created by Christopher Workman on 4/26/14.
// Copyright (c) 2014 Christopher Workman. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <QuartzCore/QuartzCore.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import "BLE.h"
#import <QuartzCore/QuartzCore.h>
@interface DiveViewController : UIViewController <CLLocationManagerDelegate, BLEDelegate> {
CGFloat compassAngle;
__weak IBOutlet UIButton *btnConnect;
__weak IBOutlet UILabel *tempLabel;
__weak IBOutlet UILabel *depthLabel;
__weak IBOutlet UILabel *maxDepthLabel;
__weak IBOutlet UILabel *pressureLabel;
__weak IBOutlet UIButton *bluetoothButton;
__weak IBOutlet UIActivityIndicatorView *indConnecting;
__weak IBOutlet UILabel *timeLabel;
__weak IBOutlet UILabel *noDecoLabel;
BOOL timerOn;
__weak IBOutlet UIButton *closeButton;
BOOL haveImage;
IBOutlet UIButton *moveButton;
}
- (IBAction)endDive:(id)sender;
- (IBAction)startRecordingData:(id)sender;
@property(nonatomic, retain) AVCaptureStillImageOutput *stillImageOutput;
@property (weak, nonatomic) IBOutlet UIView *imagePreview;
- (IBAction)snapImage:(id)sender;
- (IBAction)startCamera:(id)sender;
@property (weak, nonatomic) IBOutlet UIImageView *captureImage;
@property (nonatomic, retain) CLLocationManager *locationManager;
@property (strong, nonatomic) IBOutlet UIImageView *compassNeedle;
@property (strong, nonatomic) BLE *ble;
- (void) setMaxDepth:(int) depth andMissionName:(NSString*) missionName;
@end
//
// ViewController.m
// SeaDeep
//
// Created by Christopher Workman on 4/26/14.
// Copyright (c) 2014 Christopher Workman. All rights reserved.
//
#import "DiveViewController.h"
#import <Parse/Parse.h>
#define DegreesToRadians(x) ((x) * M_PI / 180.0)
#define DEPTH 0x0C
#define TEMP 0x0B
#define PRESSURE 0x0D
#define THEME_COLOR [UIColor colorWithRed:0.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.0f]
@interface DiveViewController ()
// get index of the current dive (ex: 1st dive of the day, 10th dive of the day, etc)
@property NSInteger currentDiveIndex;
// get the index of the current measurement (ex: 1st dive==>1st measurement, 1st dive==>4th measurement, etc)
@property NSInteger currentMeasurementIndex;
@property NSInteger recordingDuration;
@property NSInteger estimatedMaxDepth;
@property (nonatomic, strong) NSString* missionName;
@end
@implementation DiveViewController
@synthesize locationManager;
@synthesize stillImageOutput, imagePreview, captureImage;
@synthesize ble;
int timeTick = 0;
NSTimer *timer;
NSTimer *dataCollectionTimer;
int maxTime = 139*60;
int noDecoTime;
int j = 0; // vibration number
int k = 0;
- (void) setMaxDepth:(int) depth andMissionName:(NSString*) missionName{
_estimatedMaxDepth = maxDepth;
_missionName = missionName;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self prepareButtonBorders];
timeLabel.text = @"0";
locationManager=[[CLLocationManager alloc] init];
captureImage.hidden = YES;
ble = [[BLE alloc] init];
[ble controlSetup];
ble.delegate = self;
tempLabel.textColor = [UIColor colorWithRed:0 green:238 blue:235 alpha:1];
depthLabel.textColor = [UIColor colorWithRed:0 green:238 blue:235 alpha:1];
maxDepthLabel.textColor = [UIColor colorWithRed:0 green:238 blue:235 alpha:1];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.headingFilter = 1;
locationManager.delegate=self;
//Start the compass updates.
captureImage.alpha = 0.0f;
imagePreview.alpha = 0.0f;
[locationManager startUpdatingHeading];
_currentDiveIndex = [self getCurrentDiveNumber];
_currentMeasurementIndex = 0;
}
- (void) prepareButtonBorders{
tempLabel.layer.cornerRadius = 1.0f;
tempLabel.layer.borderColor = [THEME_COLOR CGColor];
tempLabel.layer.borderWidth = 1.0f;
btnConnect.layer.cornerRadius = 1.0f;
btnConnect.layer.borderColor = [THEME_COLOR CGColor];
btnConnect.layer.borderWidth = 1.0f;
pressureLabel.layer.cornerRadius = 1.0f;
pressureLabel.layer.borderColor = [THEME_COLOR CGColor];
pressureLabel.layer.borderWidth = 1.0f;
depthLabel.layer.cornerRadius = 1.0f;
depthLabel.layer.borderColor = [THEME_COLOR CGColor];
depthLabel.layer.borderWidth = 1.0f;
maxDepthLabel.layer.cornerRadius = 1.0f;
maxDepthLabel.layer.borderColor = [THEME_COLOR CGColor];
maxDepthLabel.layer.borderWidth = 1.0f;
noDecoLabel.layer.cornerRadius = 1.0f;
noDecoLabel.layer.borderColor = [THEME_COLOR CGColor];
noDecoLabel.layer.borderWidth = 1.0f;
timeLabel.layer.cornerRadius = 1.0f;
timeLabel.layer.borderColor = [THEME_COLOR CGColor];
timeLabel.layer.borderWidth = 1.0f;
}
- (void) checkIfFolderExists:(NSString*) folderPath{
//Check if folder exists, if not create folder
if (![[NSFileManager defaultManager] fileExistsAtPath:folderPath]){
[[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:NO attributes:nil error:nil];
}
}
- (NSInteger) getCurrentDiveNumber{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *diverDataFolder = [documentsDirectory stringByAppendingPathComponent:@"Diver Data"];
[self checkIfFolderExists:diverDataFolder];
NSString *missionFolder = [diverDataFolder stringByAppendingPathComponent:_missionName];
[self checkIfFolderExists:missionFolder];
NSArray *missionDivesArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:missionFolder error:NULL];
return missionDivesArray.count + 1;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
}
- (void) viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
- (IBAction)startCamera:(id)sender {
captureImage.alpha = 1.0f;
imagePreview.alpha = 1.0f;
[self initializeCamera];
moveButton.transform = CGAffineTransformMakeTranslation(900, 0);
}
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
//NSLog(@"New magnetic heading: %f", newHeading.magneticHeading);
//NSLog(@"New true heading: %f", newHeading.trueHeading);
float oldRad = -manager.heading.trueHeading * M_PI / 180.0f;
float newRad = -newHeading.trueHeading * M_PI / 180.0f;
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation"];
theAnimation.fromValue = [NSNumber numberWithFloat:oldRad];
theAnimation.toValue=[NSNumber numberWithFloat:newRad];
theAnimation.duration = 0.5f;
[_compassNeedle.layer addAnimation:theAnimation forKey:@"animateMyRotation"];
_compassNeedle.transform = CGAffineTransformMakeRotation(newRad);
//NSLog(@"%f (%f) => %f (%f)", manager.heading.trueHeading, oldRad, newHeading.trueHeading, newRad);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.2
}
NSTimer *rssiTimer;
UInt16 maxDepth = 0;
-(void)bleDidDisconnect
{
NSLog(@"->Disconnected");
btnConnect.titleLabel.font = [UIFont systemFontOfSize:39];
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
[bluetoothButton setAlpha:1.0f];
[indConnecting stopAnimating];
tempLabel.enabled = false;
//RSSILabel.text = @"---";
[rssiTimer invalidate];
}
-(void) bleDidUpdateRSSI:(NSNumber *)rssi
{
//RSSILabel.text = rssi.stringValue;
}
-(void) readRSSITimer:(NSTimer *)timer
{
[ble readRSSI];
}
-(void) bleDidConnect
{
NSLog(@"->Connected");
[btnConnect setAlpha:1.0f];
[bluetoothButton setAlpha:1.0f];
[indConnecting stopAnimating];
[self startTimer];
tempLabel.enabled = true;
// send reset
UInt8 buf[] = {0x04, 0x00, 0x00};
NSData *data = [[NSData alloc] initWithBytes:buf length:3];
[ble write:data];
// Schedule to read RSSI every 1 sec.
rssiTimer = [NSTimer scheduledTimerWithTimeInterval:(float)1.0 target:self selector:@selector(readRSSITimer:) userInfo:nil repeats:YES];
}
- (IBAction)endDive:(id)sender {
[self.navigationController setNavigationBarHidden:NO];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[self.navigationController popToRootViewControllerAnimated:YES];
}
- (IBAction)startRecordingData:(id)sender {
if (!dataCollectionTimer){
_recordingDuration = 60;
_currentMeasurementIndex++;
dataCollectionTimer = [NSTimer scheduledTimerWithTimeInterval:(float)1.0 target:self selector:@selector(readAndSaveData:) userInfo:nil repeats:YES];
[btnConnect setTitle:@"60s" forState:UIControlStateNormal];
}else{
[dataCollectionTimer invalidate];
dataCollectionTimer = nil;
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
[self uploadDataToCloud];
}
}
- (void) readAndSaveData:(NSTimer*) timer{
//this gets called every second so we will increment our timer, and save the data throughout this period.
_recordingDuration--;
if (_recordingDuration < 0){
[dataCollectionTimer invalidate];
dataCollectionTimer = nil;
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
[self uploadDataToCloud];
}else{
// here we save our data...
[btnConnect setTitle:[NSString stringWithFormat:@"%is", _recordingDuration] forState:UIControlStateNormal];
[self saveCurrentData];
}
}
- (void) saveCurrentData{
int temperature = [tempLabel.text intValue];
int depth = [depthLabel.text intValue];
int pressure = [pressureLabel.text intValue];
NSDate *currentDate = [NSDate date];
//Documetns
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//Diver Data
NSString *diverDataFolder = [documentsDirectory stringByAppendingPathComponent:@"Diver Data"];
[self checkIfFolderExists:diverDataFolder];
//"Mission"
NSString *missionFolder = [diverDataFolder stringByAppendingPathComponent:_missionName];
[self checkIfFolderExists:missionFolder];
//Dive #
NSString *diveNumber;
if (_currentDiveIndex < 10){
diveNumber = [NSString stringWithFormat:@"00%lu", (unsigned long)_currentDiveIndex];
}else{
diveNumber = [NSString stringWithFormat:@"0%lu", (unsigned long)_currentDiveIndex];
}
NSString *diveIndexFolder = [missionFolder stringByAppendingPathComponent:diveNumber];
[self checkIfFolderExists:diveIndexFolder];
//Measurement #
NSString *dataNumber;
if (_currentMeasurementIndex < 10){
dataNumber = [NSString stringWithFormat:@"00%lu", (unsigned long)_currentMeasurementIndex];
}else{
dataNumber = [NSString stringWithFormat:@"0%lu", (unsigned long)_currentMeasurementIndex];
}
NSString *dataIndexFolder = [diveIndexFolder stringByAppendingPathComponent:dataNumber];
[self checkIfFolderExists:dataIndexFolder];
//here we finally create the output path for our dictionary file
NSArray *diverDataPointsList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dataIndexFolder error:NULL];
//the next index for the file#
NSUInteger dataPointIndex = [diverDataPointsList count] + 1;
NSString *dictionaryFileName;
if (dataPointIndex < 10){
dictionaryFileName = [NSString stringWithFormat:@"00%lu.plist", (unsigned long)dataPointIndex];
}else{
dictionaryFileName = [NSString stringWithFormat:@"0%lu.plist", (unsigned long)dataPointIndex];
}
NSString *outputFilePath = [dataIndexFolder stringByAppendingPathComponent:dictionaryFileName];
NSDictionary* clipInfoDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@(temperature), @"temperature",
@(pressure), @"pressure",
@(depth), @"depth",
currentDate, @"date",
0, @"pH",
0, @"salinity",
nil];
[clipInfoDictionary writeToFile:outputFilePath atomically:NO];
}
- (void) uploadDataToCloud{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *diverDataFolder = [documentsDirectory stringByAppendingPathComponent:@"Diver Data"];
NSString *missionFolder = [diverDataFolder stringByAppendingPathComponent:_missionName];
NSString *diveNumber;
if (_currentDiveIndex < 10){
diveNumber = [NSString stringWithFormat:@"00%lu", (unsigned long)_currentDiveIndex];
}else{
diveNumber = [NSString stringWithFormat:@"0%lu", (unsigned long)_currentDiveIndex];
}
NSString *diveIndexFolder = [missionFolder stringByAppendingPathComponent:diveNumber];
NSArray *diverMeasurementsList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:diveIndexFolder error:NULL];
for (int i = 0; i < diverMeasurementsList.count; i++){
NSString *currentMeasurementItem = [diverMeasurementsList objectAtIndex:i];
NSString *measurementFolder = [diveIndexFolder stringByAppendingPathComponent:currentMeasurementItem];
NSArray *diverDataPointsList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:measurementFolder error:NULL];
for (int i = 1; i < diverDataPointsList.count; i++){
NSString *currentDataPointItem = [diverDataPointsList objectAtIndex:i];
NSString *dataPointPath = [measurementFolder stringByAppendingPathComponent:currentDataPointItem];
NSDictionary *dictFromFile = [NSDictionary dictionaryWithContentsOfFile:dataPointPath];
NSDate *date = [dictFromFile valueForKey:@"date"];
int pH = [[dictFromFile valueForKey:@"pH"] intValue];
int salinity = [[dictFromFile valueForKey:@"salinity"] intValue];
int pressure = [[dictFromFile valueForKey:@"pressure"] intValue];
int temperature = [[dictFromFile valueForKey:@"temperature"] intValue];
int depth = [[dictFromFile valueForKey:@"depth"] intValue];
PFObject *dataObject = [PFObject objectWithClassName:@"DiverData"];
dataObject[@"timeOfDataCollection"] = date;
dataObject[@"pH"] = @(pH);
dataObject[@"salinity"] = @(salinity);
dataObject[@"pressure"] = @(pressure);
dataObject[@"depth"] = @(depth);
dataObject[@"temperature"] = @(temperature);
[dataObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error){
//if there is no error then delete the data from the internal storage to free up memory
}else{
// there is an error...
NSLog(@"Error: %@", error);
}
}];
}
}
}
// When data is comming, this will be called
-(void) bleDidReceiveData:(unsigned char *)data length:(int)length
{
//NSLog(@"Length: %d", length);
for (int i = 0; i < length; i+=3)
{
//NSLog(@"0x%02X, 0x%02X, 0x%02X", data[i], data[i+1], data[i+2]);
if (data[i] == TEMP)
{
UInt16 Value;
Value = data[i+2] | data[i+1] << 8;
tempLabel.text = [NSString stringWithFormat:@"%d", Value];
}
if (data[i] == DEPTH) {
UInt16 Value2;
Value2 = data[i+2] | data[i+1] << 8;
depthLabel.text = [NSString stringWithFormat:@"%d", Value2];
if (Value2 > maxDepth) {
maxDepth = Value2;
}
maxDepthLabel.text = [NSString stringWithFormat:@"%d", maxDepth];
}
if (data[i] == PRESSURE) {
UInt16 Value3;
Value3 = data[i+2] | data[i+1] << 8;
pressureLabel.text = [NSString stringWithFormat:@"%d", Value3];
if (Value3 <= 300 && j < 5) {
j++;
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
}
}
}
if (maxDepth <= 35) {
maxTime = 139*60; // time in seconds
}
else if (maxDepth <= 40) {
maxTime = 104*60;
}
else if (maxDepth <= 50) {
maxTime = 63*60;
}
else if (maxDepth <= 60) {
maxTime = 47*60;
}
else if (maxDepth <= 70) {
maxTime = 33*60;
}
else if (maxDepth <= 80) {
maxTime = 25*60;
}
else if (maxDepth <= 90) {
maxTime = 21*60;
}
else if (maxDepth > 90) {
maxTime = 3*60;
}
}
#pragma mark - Actions
// Connect button will call to this
- (IBAction)btnScanForPeripherals:(id)sender
{
maxTime = 139*60;
noDecoLabel.text = [[NSString alloc] initWithFormat:@"%d", 0];
if (ble.activePeripheral)
if(ble.activePeripheral.state == CBPeripheralStateConnected)
{
[[ble CM] cancelPeripheralConnection:[ble activePeripheral]];
btnConnect.titleLabel.font = [UIFont systemFontOfSize:39];
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
timerOn = NO;
return;
}
if (ble.peripherals)
ble.peripherals = nil;
[btnConnect setEnabled:false];
[ble findBLEPeripherals:2];
[NSTimer scheduledTimerWithTimeInterval:(float)2.0 target:self selector:@selector(connectionTimer:) userInfo:nil repeats:NO];
[bluetoothButton setAlpha:0.0f];
[indConnecting setFrame:bluetoothButton.frame];
[indConnecting setAlpha:1.0f];
//[btnConnect setAlpha:0.0f];
[indConnecting startAnimating];
}
-(void) connectionTimer:(NSTimer *)timer
{
[btnConnect setEnabled:true];
btnConnect.titleLabel.font = [UIFont systemFontOfSize:23];
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
timerOn = YES;
timeTick = 0;
maxDepth = 0;
if (ble.peripherals.count > 0)
{
[ble connectPeripheral:[ble.peripherals objectAtIndex:0]];
}
else
{
btnConnect.titleLabel.font = [UIFont systemFontOfSize:39];
[btnConnect setTitle:@"DIVE" forState:UIControlStateNormal];
timerOn = NO;
[bluetoothButton setAlpha:1.0f];
[indConnecting stopAnimating];
}
}
- (void)startTimer {
if (timerOn == YES) {
//timerOn = YES;
timeTick = 0;
}
// else if (timerOn == YES) {
// timerOn = NO;
// }
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
}
-(void)tick{
if (timerOn == YES) {
timeTick++;
int timeTickHours = timeTick / 3600;
int timeTickMinutes = timeTick / 60 - 60 * timeTickHours;
int timeTickSeconds = timeTick % 60;
NSString *timeTickHrLabel = [[NSString alloc] initWithFormat:@"%d", timeTickHours];
NSString *timeTickMinLabel = [[NSString alloc] initWithFormat:@":%d", timeTickMinutes];
NSString *timeTickSecLabel = [[NSString alloc] initWithFormat:@":%d", timeTickSeconds];
NSString *timeTickLabelMinSec = [timeTickMinLabel stringByAppendingString:timeTickSecLabel];
NSString *timeString = [timeTickHrLabel stringByAppendingString:timeTickLabelMinSec];
timeLabel.text = timeString;
}
noDecoTime = maxTime - timeTick;
int noDecoHours = noDecoTime / 3600;
int noDecoMinutes = noDecoTime / 60 - 60 * noDecoHours;
int noDecoSeconds = noDecoTime % 60;
NSString *noDecoHrLabel = [[NSString alloc] initWithFormat:@"%d", noDecoHours];
NSString *noDecoMinLabel = [[NSString alloc] initWithFormat:@":%d", noDecoMinutes];
NSString *noDecoSecLabel = [[NSString alloc] initWithFormat:@":%d", noDecoSeconds];
NSString *noDecoLabelMinSec = [noDecoMinLabel stringByAppendingString:noDecoSecLabel];
NSString *noDecoLabelText = [noDecoHrLabel stringByAppendingString:noDecoLabelMinSec];
noDecoLabel.text = noDecoLabelText;
if (noDecoTime < 25 * 60 && k < 5) {
k++;
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
}
}
- (IBAction)snapImage:(id)sender {
moveButton.transform = CGAffineTransformMakeTranslation(-900, 0);
if (!haveImage) {
captureImage.image = nil; //remove old image from view
captureImage.hidden = NO; //show the captured image view
imagePreview.hidden = YES; //hide the live video feed
[self capImage];
}
else {
captureImage.hidden = YES;
imagePreview.hidden = NO;
haveImage = NO;
}
}
//AVCaptureSession to show live video feed in view
- (void) initializeCamera {
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetPhoto;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
[captureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
captureVideoPreviewLayer.frame = self.imagePreview.bounds;
imagePreview.transform = CGAffineTransformMakeRotation(M_PI/2);
[self.imagePreview.layer addSublayer:captureVideoPreviewLayer];
UIView *view = [self imagePreview];
CALayer *viewLayer = [view layer];
[viewLayer setMasksToBounds:YES];
CGRect bounds = [view bounds];
[captureVideoPreviewLayer setFrame:bounds];
NSArray *devices = [AVCaptureDevice devices];
AVCaptureDevice *frontCamera;
AVCaptureDevice *backCamera;
for (AVCaptureDevice *device in devices) {
NSLog(@"Device name: %@", [device localizedName]);
if ([device hasMediaType:AVMediaTypeVideo]) {
if ([device position] == AVCaptureDevicePositionBack) {
NSLog(@"Device position : back");
backCamera = device;
}
else {
NSLog(@"Device position : front");
frontCamera = device;
}
}
}
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:frontCamera error:&error];
if (!input) {
NSLog(@"ERROR: trying to open camera: %@", error);
}
[session addInput:input];
stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys: AVVideoCodecJPEG, AVVideoCodecKey, nil];
[stillImageOutput setOutputSettings:outputSettings];
[session addOutput:stillImageOutput];
[session startRunning];
}
- (void) capImage { //method to capture image from AVCaptureSession video feed
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
videoConnection = connection;
break;
}
}
if (videoConnection) {
break;
}
}
NSLog(@"about to request a capture from: %@", stillImageOutput);
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (imageSampleBuffer != NULL) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
[self processImage:[UIImage imageWithData:imageData]];
}
}];
}
- (void) processImage:(UIImage *)image { //process captured image, crop, resize and rotate
haveImage = YES;
if([UIDevice currentDevice].userInterfaceIdiom==UIUserInterfaceIdiomPad) { //Device is ipad
// Resize image
UIGraphicsBeginImageContext(CGSizeMake(768, 1022));
[image drawInRect: CGRectMake(0, 0, 768, 1022)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect cropRect = CGRectMake(0, 130, 768, 768);
CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);
//or use the UIImage wherever you like
[captureImage setImage:[UIImage imageWithCGImage:imageRef]];
CGImageRelease(imageRef);
}else{ //Device is iphone
// Resize image
UIGraphicsBeginImageContext(CGSizeMake(320, 426));
[image drawInRect: CGRectMake(0, 0, 320, 426)];
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect cropRect = CGRectMake(0, 55, 320, 320);
CGImageRef imageRef = CGImageCreateWithImageInRect([smallImage CGImage], cropRect);
[captureImage setImage:[UIImage imageWithCGImage:imageRef]];
captureImage.transform = CGAffineTransformMakeRotation(M_PI/2);
UIImageWriteToSavedPhotosAlbum(captureImage.image, nil, nil, nil);
CGImageRelease(imageRef);
}
//adjust image orientation based on device orientation
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeLeft) {
NSLog(@"landscape left image");
[UIView beginAnimations:@"rotate" context:nil];
[UIView setAnimationDuration:0.5];
captureImage.transform = CGAffineTransformMakeRotation(DegreesToRadians(-90));
[UIView commitAnimations];
}
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationLandscapeRight) {
NSLog(@"landscape right");
[UIView beginAnimations:@"rotate" context:nil];
[UIView setAnimationDuration:0.5];
captureImage.transform = CGAffineTransformMakeRotation(DegreesToRadians(90));
[UIView commitAnimations];
}
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown) {
NSLog(@"upside down");
[UIView beginAnimations:@"rotate" context:nil];
[UIView setAnimationDuration:0.5];
captureImage.transform = CGAffineTransformMakeRotation(DegreesToRadians(180));
[UIView commitAnimations];
}
if ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) {
NSLog(@"upside upright");
[UIView beginAnimations:@"rotate" context:nil];
[UIView setAnimationDuration:0.5];
captureImage.transform = CGAffineTransformMakeRotation(DegreesToRadians(0));
[UIView commitAnimations];
}
}
@end
//
// MissionsViewController.h
// SeaDeep
//
// Created by Julio Vasquez on 3/6/15.
// Copyright (c) 2015 Christopher Workman. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MissionsViewController : UIViewController
@end
@import UIKit; // Apple
@interface WunderController : UIViewController
@end
#import "WunderController.h" // Header
#import <Relayr/Relayr.h> // Relayr.framework
#define RelayrAppID @"72b0e324-74bf-4e07-82a5-da53e0133a1e"
#define RelayrAppSecret @"_ifE7jQHgZ9DKi5-TiLBXQOAT6ibp6Zf"
#define RelayrAppRedirectURI @"https://relayr.io"
@interface WunderController ()
@property (weak,nonatomic) IBOutlet UILabel* currentTempLabel;
@property (weak,nonatomic) IBOutlet UILabel* currentHumidLabel;
@end
@implementation WunderController
#pragma mark - Public API
- (void)viewDidLoad
{
static RelayrApp* storedApp;
[super viewDidLoad];
// Retrieve the RelayrApp that you have created on the developer dashboard.
[RelayrApp appWithID:RelayrAppID OAuthClientSecret:RelayrAppSecret redirectURI:RelayrAppRedirectURI completion:^(NSError* error, RelayrApp* app) {
if (error) { return NSLog(@"There was an error retrieving the RelayrApp: %@", error); }
storedApp = app;
// Sign in an user into your Relayr App.
[app signInUser:^(NSError* error, RelayrUser* user) {
if (error) { return NSLog(@"There was an error signing the user: %@", error); }
// Retrieve the transmitters and devices owned by the user.
[user queryCloudForIoTs:^(NSError* error) {
if (error) { return NSLog(@"There was an error retrieving the users IoT."); }
// To simplify, we suppose that the user has only one transmitter (wunderbar)
RelayrTransmitter* transmitter = user.transmitters.anyObject;
if (!transmitter) { return NSLog(@"The user has no wunderbars."); }
// The Relayr cloud mantains a specific list of "meanings" specifying the capabilities of devices. In this case we are interested in "temperature"
RelayrDevice* device = [transmitter devicesWithReadingMeanings:@[@"temperature"]].anyObject;
if (!device) { return NSLog(@"The user hasn't onboard the temperature sensor."); }
[device subscribeToAllReadingsWithBlock:^(RelayrDevice *device, RelayrReading* input, BOOL* unsubscribe) {
if ([input.meaning isEqualToString:@"temperature"])
{
_currentTempLabel.text = [NSString stringWithFormat:@"%@ C", input.value];
}
else if ([input.meaning isEqualToString:@"humidity"])
{
_currentHumidLabel.text = [NSString stringWithFormat:@"%@ %%", input.value];
}
} error:^(NSError *error) {
NSLog(@"%@", error.localizedDescription);
}];
}];
}];
}];
}
// Make the status bar white for the blue-ish background :)
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
@end
//
// MainMenuViewController.h
// SeaDeep
//
// Created by Julio Vasquez on 3/5/15.
// Copyright (c) 2015 Christopher Workman. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface MainMenuViewController : UIViewController
@end
//
// MainMenuViewController.m
// SeaDeep
//
// Created by Julio Vasquez on 3/5/15.
// Copyright (c) 2015 Christopher Workman. All rights reserved.
//
#import "MainMenuViewController.h"
#define THEME_COLOR [UIColor colorWithRed:0.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:1.0f]
@interface MainMenuViewController ()
@property (weak, nonatomic) IBOutlet UIButton *diveButton;
@property (weak, nonatomic) IBOutlet UIButton *profileButton;
@property (weak, nonatomic) IBOutlet UIButton *missionButton;
@property (weak, nonatomic) IBOutlet UIButton *atmosphereButton;
@property (weak, nonatomic) IBOutlet UIButton *directoryButton;
@end
@implementation MainMenuViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:NO animated:YES];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[self prepareButtons];
// Do any additional setup after loading the view.
}
- (void) prepareButtons{
_diveButton.layer.cornerRadius = _diveButton.frame.size.height/2;
_diveButton.layer.borderWidth = 2.0f;
_diveButton.layer.borderColor = [THEME_COLOR CGColor];
_missionButton.layer.cornerRadius = 1;
_missionButton.layer.borderWidth = 2.0f;
_missionButton.layer.borderColor = [THEME_COLOR CGColor];
_profileButton.layer.cornerRadius = 1;
_profileButton.layer.borderWidth = 2.0f;
_profileButton.layer.borderColor = [THEME_COLOR CGColor];
_atmosphereButton.layer.cornerRadius = 1;
_atmosphereButton.layer.borderWidth = 2.0f;
_atmosphereButton.layer.borderColor = [THEME_COLOR CGColor];
}
- (void) viewWillAppear:(BOOL)animated{
[self.navigationController setNavigationBarHidden:NO animated:YES];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
Comments