/* * Copyright Cypress Semiconductor Corporation, 2014-2015 All rights reserved. * * This software, associated documentation and materials ("Software") is * owned by Cypress Semiconductor Corporation ("Cypress") and is * protected by and subject to worldwide patent protection (UnitedStates and foreign), United States copyright laws and international * treaty provisions. Therefore, unless otherwise specified in a separate license agreement between you and Cypress, this Software * must be treated like any other copyrighted material. Reproduction, * modification, translation, compilation, or representation of this * Software in any other form (e.g., paper, magnetic, optical, silicon) * is prohibited without Cypress's express written permission. * * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY * KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, * NONINFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE. Cypress reserves the right to make changes * to the Software without notice. Cypress does not assume any liability * arising out of the application or use of Software or any product or * circuit described in the Software. Cypress does not authorize its * products for use as critical components in any products where a * malfunction or failure may reasonably be expected to result in * significant injury or death ("High Risk Product"). By including * Cypress's product in a High Risk Product, the manufacturer of such * system or application assumes all risk of such use and in doing so * indemnifies Cypress against all liability. * * Use of this Software may be limited by and subject to the applicable * Cypress software license agreement. * * */ #import "HRMModel.h" #import "CBMoralManager.h" // Body location #define OTHER @"Other" #define CHEST @"Chest" #define WRIST @"Wrist" #define FINGER @"Finger" #define HAND @"Hand" #define EAR_LOBE @"Ear Lobe" #define FOOT @"Foot" #define LOCATION_NA @"Body Location: N/A" /*! * @class HRMModel * * @discussion Class to handle the heart rate measurement service related operations * */ @interface HRMModel() { void (^cbCharacteristicHandler)(BOOL success, NSError *error); void (^cbCharacteristicDiscoverHandler)(BOOL success, NSError *error); } @end @implementation HRMModel @synthesize bpmValue; @synthesize sensorLocation; @synthesize RR_Interval; @synthesize EnergyExpended; /*! * @method startDiscoverChar: * * @discussion Discovers the specified characteristics of a service.. */ -(void)startDiscoverChar:(void (^) (BOOL success, NSError *error))handler { cbCharacteristicDiscoverHandler = handler; [[CBMoralManager sharedManager] setCbCharacteristicDelegate:self]; [[[CBMoralManager sharedManager] myPeripheral] discoverCharacteristics:nil forService:[[CBMoralManager sharedManager] myService]]; } /*! * @method updateCharacteristicWithHandler: * * @discussion Sets notifications or indications for the value of a specified characteristic. */ -(void)updateCharacteristicWithHandler:(void (^) (BOOL success, NSError *error))handler { cbCharacteristicHandler = handler; } /*! * @method stopUpdate * * @discussion Stop notifications or indications for the value of a specified characteristic. */ -(void)stopUpdate { cbCharacteristicHandler = nil; if ([[[CBMoralManager sharedManager] myService].UUID isEqual:HRM_HEART_RATE_SERVICE_UUID]) { for (CBCharacteristic *aChar in [[CBMoralManager sharedManager] myService].characteristics) { if ([aChar.UUID isEqual:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID]) { if (aChar.isNotifying) { [[[CBMoralManager sharedManager] myPeripheral] setNotifyValue:NO forCharacteristic:aChar]; [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:HRM_HEART_RATE_SERVICE_UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID] descriptor:nil operation:STOP_NOTIFY]; } cbCharacteristicDiscoverHandler(YES,nil); break; } } } } #pragma mark - CBCharecteristicManger /*! * @method peripheral: didDiscoverCharacteristicsForService: error: * * @discussion Method invoked when characteristics are discovered for a service * */ -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if ([service.UUID isEqual:HRM_HEART_RATE_SERVICE_UUID]){ for (CBCharacteristic *aChar in service.characteristics){ if ([aChar.UUID isEqual:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID]){ [[[CBMoralManager sharedManager] myPeripheral] setNotifyValue:YES forCharacteristic:aChar]; [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:HRM_HEART_RATE_SERVICE_UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID] descriptor:nil operation:START_NOTIFY]; cbCharacteristicDiscoverHandler(YES,nil); } else if([aChar.UUID isEqual:HRM_BODY_LOCATION_CHARACTERISTIC_UUID]) { [[[CBMoralManager sharedManager] myPeripheral] readValueForCharacteristic:aChar]; [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:HRM_HEART_RATE_SERVICE_UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:HRM_BODY_LOCATION_CHARACTERISTIC_UUID] descriptor:nil operation:READ_REQUEST]; } } } } /*! * @method peripheral: didUpdateValueForCharacteristic: error: * * @discussion Method invoked when the characteristic value changes * */ -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if(error == nil) { if ([characteristic.UUID isEqual:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID]) { [self getHeartBPMData:characteristic error:error]; } else if ([characteristic.UUID isEqual:HRM_BODY_LOCATION_CHARACTERISTIC_UUID]) { [self getBodyLocation:characteristic]; } cbCharacteristicHandler(YES,nil); } else { cbCharacteristicHandler(NO,error); } } /*! * @method getHeartBPMData:error * * @discussion Method to get the Heart Rate Measurement Value , Energy Expended, RR-Interval * */ // Instance method to get the heart rate BPM information - (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error { // Get the BPM // // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml // // Convert the contents of the characteristic value to a data-object // NSData *data = [characteristic value]; // Get the byte sequence of the data-object // const uint8_t *reportData = [data bytes]; // Initialise the offset variable // NSUInteger offset = 1; // Initialise the bpm variable // uint16_t bpm = 0; // Next, obtain the first byte at index 0 in the array as defined by reportData[0] and mask out all but the 1st bit // // The result returned will either be 0, which means that the 2nd bit is not set, or 1 if it is set // // If the 2nd bit is not set, retrieve the BPM value at the second byte location at index 1 in the array // if ((reportData[0] & 0x01) == 0) { // Retrieve the BPM value for the Heart Rate Monitor bpm = reportData[1]; offset = offset + 1; // Plus 1 byte // } else { // If the second bit is set, retrieve the BPM value at second byte location at index 1 in the array and // // convert this to a 16-bit value based on the host’s native byte order // bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[1])); offset = offset + 2; // Plus 2 bytes // } self.bpmValue = bpm;//[NSString stringWithFormat:@"%d",bpm]; // Determine if EE data is present // // If the 3rd bit of the first byte is 1 this means there is EE data // // If so, increase offset with 2 bytes // if (reportData[0] & 0x08) { uint16_t expendedEnergy = 0; expendedEnergy = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[offset])); offset = offset + 2; // Plus 2 bytes // self.EnergyExpended = [NSString stringWithFormat:@"%d",expendedEnergy]; } else { self.EnergyExpended = @"0"; } // Determine if RR-interval data is present // // If the 4th bit of the first byte is 1 this means there is RR data // if (reportData[0] & 0x10) { // The number of RR-interval values is total bytes left / 2 (size of uint16) // NSUInteger length = [data length]; NSUInteger count = (length - offset)/2; uint16_t RRinterval = 0 ; for (int i = 0; i < count; i++) { // The unit for RR interval is 1/1024 seconds // RRinterval = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[offset])); RRinterval = ((double)RRinterval / 1024.0 ) * 1000.0; offset = offset + 2; // Plus 2 bytes // self.RR_Interval = [NSString stringWithFormat:@"%d",RRinterval]; } } [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:HRM_HEART_RATE_SERVICE_UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:HRM_NOTIFICATIONS_CHARACTERISTIC_UUID] descriptor:nil operation:[NSString stringWithFormat:@"%@%@ %@",NOTIFY_RESPONSE,DATA_SEPERATOR,[Utilities convertDataToLoggerFormat:data]]]; } /*! * @method getSensorContactStatus: * * @discussion Instance method to get the body location of the device is available or not * */ -(BOOL)getSensorContactStatus:(CBCharacteristic *)characteristic { NSData *data = [characteristic value]; // 1 const uint8_t *reportData = [data bytes]; if((reportData[0] & 0x02) == 4) return YES; return NO; } /*! * @method getBodyLocation: * * @discussion Instance method to get the body location of the device * */ // - (void) getBodyLocation:(CBCharacteristic *)characteristic { NSData *sensorData = [characteristic value]; uint8_t *bodyData = (uint8_t *)[sensorData bytes]; if (bodyData ) { uint8_t bodyLocation = bodyData[0]; NSString *Sensloc = @""; switch (bodyLocation) { case 0: Sensloc = OTHER; break; case 1: Sensloc = CHEST; break; case 2: Sensloc = WRIST; break; case 3: Sensloc = FINGER; break; case 4: Sensloc = HAND; break; case 5: Sensloc = EAR_LOBE; break; case 6: Sensloc = FOOT; break; default: break; } self.sensorLocation = Sensloc; } else { self.sensorLocation = LOCATION_NA; } [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:characteristic.service.UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:characteristic.UUID] descriptor:nil operation:[NSString stringWithFormat:@"%@%@ %@",READ_RESPONSE,DATA_SEPERATOR,[Utilities convertDataToLoggerFormat:sensorData]]]; } @end