Cycling Speed & Cadence

From Polar Developers
Jump to: navigation, search


General

On this page you will find the operational flow diagrams for the cycling speed and cadence sensors. You can also find links to the used Bluetooth specifications and example codes.

Services in use

Cycling Speed and Cadence: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.cycling_speed_and_cadence.xml
Device information: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml
Battery: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xml

Flow diagrams



Back to top

Cycling example code for iOS

// CSC ( Cycling Speed And Cadence) example code

#define CSC_SERVICE @"1816"
#define CSC_MEASUREMENT @"2A5B"

// in h file
@interface whatIsYourBluetoothInterface : NSObject< CBCentralManagerDelegate, CBPeripheralDelegate>
{
	// for CSC data handling
	int wheel_size_;
    unsigned int curr_wheel_revs_;
    unsigned int curr_crank_revs_;
    double distance_;
    double total_distance_;
    double prev_wheel_event_;
    double curr_wheel_event_;
    double curr_crank_event_;
    double prev_crank_event_;
    unsigned int prev_wheel_revs_;
    unsigned int prev_crank_revs_;
    unsigned int exer_wheel_revs_;
    unsigned int last_wheel_rounds_;
    time_t last_wheel_event_;
    time_t last_crank_event_;
    double last_speed_;
    int last_cadence_;
    int max_wheel_time_;

}

// in .m file

struct cscflags
{
	uint8_t _wheel_revolutions_present:1;
	uint8_t _crank_revolutions_present:1;
	uint8_t _reserved:6;
};

#define MIN_SPEED_VALUE 2.5
#define MAX_CEVENT_TIME 4
#define MAX_EVENT_TIME  64.0
#define KSPEED_UNINIT -1
#define KCADENCE_UNINT -1

- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)aPeripheral
{
	prev_wheel_event_ = 0;
	curr_wheel_event_ = 0;
	curr_crank_event_ = 0;
	prev_crank_event_ = 0;
	prev_wheel_revs_  = 0;
	curr_wheel_revs_  = 0;
	prev_crank_revs_  = 0;
	curr_crank_revs_  = 0;
	exer_wheel_revs_  = 0;
	last_wheel_event_ = 0;
	last_wheel_rounds_ = 0;
	last_crank_event_  = 0;
	last_speed_ = KSPEED_UNINIT;
	last_cadence_ = KCADENCE_UNINT;
	wheel_size_ = 2000; // default 2000 mm wheel size
    max_wheel_time_ = (int)((wheel_size_*0.001*1*3.6)/MIN_SPEED_VALUE);
	
	[aPeripheral setDelegate:self];
	// NOTE you might only discover RSC service, but on this example we discover all services 
    [aPeripheral discoverServices:nil];
}

- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error
{
	if(!error)
	{
		for (CBService *aService in aPeripheral.services)
		{
			if( [aService.UUID isEqual:[CBUUID UUIDWithString:CSC_SERVICE]] )
			{
				[aPeripheral discoverCharacteristics:nil forService:aService];
			}
		}
	}
}

- (void) peripheral:(CBPeripheral *)aPeripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
	if(!error)
	{
		if( [aService.UUID isEqual:[CBUUID UUIDWithString:CSC_SERVICE]] )
		{
			for (CBCharacteristic *aChar in service.characteristics)
            {
                if( [aChar.UUID isEqual:[CBUUID UUIDWithString:CSC_MEASUREMENT]] )
                {
                    [aPeripheral setNotifyValue:YES forCharacteristic:aChar];
                }
            }
		}
	}
}

- (void) peripheral:(CBPeripheral *)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
	if(!error)
	{
		if( [characteristic.UUID isEqual:[CBUUID UUIDWithString:CSC_MEASUREMENT]] )
		{
			const uint8_t *report_data( (const uint8_t*)[characteristic.value bytes] );
			int data_size( [characteristic.value length] );
			
			cscflags flags={0};
			memcpy(&flags,report_data,sizeof(flags));
			int offset(sizeof(flags));
			
			time_t abs_time = time(NULL);

			// if speed present can be combo sensor or speed sensor only
			if( flags._wheel_revolutions_present )
			{
				prev_wheel_revs_ = curr_wheel_revs_;

				memcpy(&curr_wheel_revs_,report_data+offset,4);
				offset += 4;

				prev_wheel_event_ = curr_wheel_event_;

				uint16_t tmp_curr_wheel_event_(0);
				memcpy(&tmp_curr_wheel_event_,report_data+offset,sizeof(tmp_curr_wheel_event_));
				curr_wheel_event_ = (double)tmp_curr_wheel_event_/1024.0;
				offset += sizeof(tmp_curr_wheel_event_);

				if( last_speed_ == KSPEED_UNINIT )
				{
					// skip first packet
					last_speed_ = 0;
					last_wheel_event_  = abs_time;
				}
				else
				{
					double event_diff = curr_wheel_event_;
					if( prev_wheel_event_ )
					{
						if( prev_wheel_event_ <= curr_wheel_event_ )
							event_diff = curr_wheel_event_ - prev_wheel_event_;
						else
						{
							// rollover
							event_diff = curr_wheel_event_ + ( ((double)0xFFFF/1024.0) - prev_wheel_event_);
						}
					}

					unsigned int wheel_rounds(curr_wheel_revs_ - prev_wheel_revs_);

					if( curr_wheel_revs_ < prev_wheel_revs_ )
					{
						prev_wheel_revs_ = curr_wheel_revs_;
						wheel_rounds = 0;
					}

					if( wheel_rounds > 0 )	last_wheel_rounds_ = wheel_rounds;

					exer_wheel_revs_ += wheel_rounds;
					double speed(0);
					if( (!event_diff || !wheel_rounds) && (abs_time-last_wheel_event_) < max_wheel_time_ )
					{
						speed = last_speed_;
						exer_wheel_revs_ += last_wheel_rounds_;
					}
					if( event_diff && wheel_rounds )
					{
						speed = ((((double)wheel_size_*0.001)*wheel_rounds)/event_diff)*3.6;
						last_wheel_event_ = abs_time;
					}

					last_speed_ = speed;
					distance_ = ((double)wheel_size_*0.001)*exer_wheel_revs_; // in meters
					total_distance_ = ((double)wheel_size_*0.001)*curr_wheel_revs_; // in meters, assumption that the wheel size has been the same 
				}
			}
			
			// if cadence present can be combo sensor or cadence sensor only
			if( flags._crank_revolutions_present )
			{
				prev_crank_revs_ = curr_crank_revs_;
				
				memcpy(&curr_crank_revs_ ,report_data+offset,2);
				offset += 2;

				int crank_rounds( curr_crank_revs_ - prev_crank_revs_ );
				if( curr_crank_revs_ < prev_crank_revs_ )
				{
					prev_crank_revs_ = (0xFFFF - prev_crank_revs_);
					crank_rounds = curr_crank_revs_ - prev_crank_revs_;
				}

				prev_crank_event_ = curr_crank_event_;

				uint16_t tmp_curr_crank_event_(0)
				memcpy(&tmp_curr_crank_event_,report_data+offset,sizeof(tmp_curr_crank_event_));
				offset += sizeof(tmp_curr_crank_event_);
				
				curr_crank_event_ = (double)tmp_curr_crank_event_/1024.0;
				if( last_cadence_ == KCADENCE_UNINT )
				{
					// skip first packet
					last_crank_event_ = abs_time;
					last_cadence_ = 0;
				}
				else
				{
					double event_diff = curr_crank_event_;

					if( prev_crank_event_ )
					{
						if( prev_crank_event_ <= curr_crank_event_ )
							event_diff = curr_crank_event_ - prev_crank_event_;
						else
						{
							// rollover
							event_diff = curr_crank_event_ + ( ((double)0xFFFF/1024.0) - prev_crank_event_);
						}
					}

					if( (!event_diff || !crank_rounds) && (abs_time-last_crank_event_) < MAX_CEVENT_TIME )
					{
						// do nothing last_cadence_
					}
					else if( event_diff && crank_rounds )
					{
						last_cadence_ = (int)((crank_rounds/event_diff)*60.0);
						last_crank_event_ = abs_time;
					}
					else
					{
						last_cadence_ = 0;
					}
				}
			}
		}
    }
}




Back to top

Cycling example code for Android

Example codes for Android implementation can be found here on the Android developer site




Back to top

Other Sensors



Back to top