iOS Development

Post questions and issues with Concept2 PM3 SDK
Piboo
Paddler
Posts: 12
Joined: April 7th, 2015, 6:33 pm

iOS Development

Post by Piboo » April 8th, 2015, 6:13 am

Hi,

I'm new on this forum and also new as a computer software.
I'm trying to develop an application like ergdata for iPhone but the documentation is quite horrible and the firm doesn't answer to my questions about it.

So I would like to know if someone are developing on this platform and would be great to share some experiences and snippet.

For information I use swift to code.

Does anyone succeed to connect an app to its rower ?

I put the 3 header files in my project and try some commands that i found in several topic but all are in c++.

Thank you for your help.

Piboo

horseshoe7
Paddler
Posts: 15
Joined: April 21st, 2015, 6:28 am

Re: iOS Development

Post by horseshoe7 » April 21st, 2015, 6:36 am

I just bought the PM5 retrofit kit for my Model B rower because I want to be able to develop an iOS App. I also have interest in the SDK.

So far, I found that the SDK provided on the website has an Xcode 3.0 Project file that won't build on OSX 10.10 (Yosemite).

I would be grateful for anyone who could help point me in the right direction. I'm primarily an iOS Developer but can do Mac apps, and I do have *some* understanding of C++, but it's not really my language.

I would be willing to collaborate so to publish an iOS SDK if anyone is interested.

Cheers
horseshoe7.wordpress.com

robnotyou
Paddler
Posts: 8
Joined: May 21st, 2015, 4:26 am
Location: Kettering, UK

Re: iOS Development

Post by robnotyou » May 21st, 2015, 5:06 am

For PM5 and iOS, I'd suggest leaving the SDK to one side for now, and starting with a standard Bluetooth app.

PM5 and iOS can communicate using Bluetooth 4.0 (also called "Bluetooth LE" and "Bluetooth Smart").
The Concept2 Bluetooth specification is on their website here: http://www.concept2.com/files/pdf/us/mo ... nition.pdf

PM5 uses four Bluetooth "Services", but you can get started using just two of them:

Device Discovery Service, UUID: CE060000-43E5-11E4-916C-0800200C9A66
This is just used to discover and connect the PM5

Device Information Service UUID: CE060010-43E5-11E4-916C-0800200C9A66
This Service has 4 Characteristics, for things like serial number and version numbers.

So in Swift, you might have something like this:

// Service UUIDs
let BLEBaseServiceUUID: CBUUID = CBUUID(string: "CE060000-43E5-11E4-916C-0800200C9A66") // Device Discovery
let BLEInformationServiceUUID: CBUUID = CBUUID(string: "CE060010-43E5-11E4-916C-0800200C9A66") // Device Information

Then you could start a bluetooth scan, and pass it the array of Service UUIDs to scan for:
let BLEServiceUUIDArray: [CBUUID]? = [BLEBaseServiceUUID, BLEInformationServiceUUID]

Once basic communication is working, you can look at the more exciting "Rowing" Service:
Rowing Service, UUID: CE060030-43E5-11E4-916C-0800200C9A66
...which has 11 Characteristics which package up lots of live rower data.

And then (eventually) you might want to look at the CSAFE commands:
Control Service, UUID: CE060020-43E5-11E4-916C-0800200C9A66
(And at that point, you'll want to look at the SDK documentation!)


One hint:
Don't develop an app like ErgData, 'coz that already exists. Do something new and exciting!

- Rob.

User avatar
Carl Watts
Marathon Poster
Posts: 4688
Joined: January 8th, 2010, 4:35 pm
Location: NEW ZEALAND

Re: iOS Development

Post by Carl Watts » May 21st, 2015, 6:14 pm

robnotyou wrote:

One hint:
Don't develop an app like ErgData, 'coz that already exists. Do something new and exciting!

- Rob.
Yes like contact Digital rowing and offer to design their RowPro program as an App.

Why re-invent the wheel, the software is already out there and has already undergone ten years of development, the main problem these days is there are too many OS on different devices to get a single decent App running on all of them for the resources of a small company. There is also not a lot of money in this field due to the relatively limited number of potential users so loads of one man bands writing multiple Apps just doesn't make sense, the result is just poor software that splits the user base.

The ultimate software will enable you to go online at any time of the day or night and find 16 people already online wanting to row together.
Carl Watts.
Age:56 Weight: 108kg Height:183cm
Concept 2 Monitor Service Technician & indoor rower.
http://log.concept2.com/profile/863525/log

Live Rowing
Paddler
Posts: 23
Joined: May 22nd, 2015, 10:22 pm

Re: iOS Development

Post by Live Rowing » May 22nd, 2015, 10:54 pm

Carl, Check out the LiveRowing website - Liverowing.net We have been in development since July of 2014. We are almost there! Beat coming in the next couple of weeks. A major tech media outlet is doing a piece on us next week in SanFran so be on the look out.

The LiveRowing Team
Denver

wivku
Paddler
Posts: 21
Joined: December 19th, 2014, 12:03 pm

Re: iOS Development

Post by wivku » June 26th, 2015, 1:11 pm

Thanks Rob, very useful!
I am starting to learn Swift (and talking to my PM5 using BLE) and using your hints I have indeed been able to discover and connect to my PM5.

Would you / anyone happen to have e.g. a complete class, so that I don't have to manually create all the UUID's and functions to read/write and pack/unpack the values?
A la: https://github.com/anas-imtiaz/SwiftSen ... rTag.swift

Related, in the tutorial for that one ( http://anasimtiaz.com/?p=201 ), they write a byte to subscribe to notifications:

Code: Select all

self.sensorTagPeripheral.writeValue(enablyBytes, forCharacteristic: thisCharacteristic, type: CBCharacteristicWriteType.WithResponse)
Is that a generic BLE way to subscribe, or specific for the sensortTag? I.e. what should I do to subscribe to live rowing service data?

horseshoe7
Paddler
Posts: 15
Joined: April 21st, 2015, 6:28 am

Re: iOS Development

Post by horseshoe7 » July 6th, 2015, 8:35 am

Why re-invent the wheel, the software is already out there and has already undergone ten years of development....
That's the whole point of an SDK or a framework. To add a layer of abstraction that makes it easier for others to use, and is not specific to any particular app. I don't like the current software out there. It also doesn't do what I want it to do, namely, take my data and be able to add it to my runkeeper, Strava, as an activity, without having to manually upload my activities from within Strava itself.

For me, an iOS SDK is a set of classes that makes communication with the Rower straightforward. Which wraps the specifics of the bluetooth specification into an easy-to-use Objective-C / Swift flavoured API, and has nothing to do with a specific app.

What Concept2 makes available as an "SDK" is on iOS not an SDK at all.

I hope to set about changing this if I find the time.

Many thanks robnotyou !! Great help.

Piboo
Paddler
Posts: 12
Joined: April 7th, 2015, 6:33 pm

Re: iOS Development

Post by Piboo » July 7th, 2015, 6:22 am

Hi,

Thnak your very murch for these answers.
I didn't see the notifications.

We had the same approach since my first mail but I can't discover yet the rower.

var rowingTagPeripheral:CBPeripheral!

func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {

let nameOfDeviceFound:NSString = (advertisementData as NSDictionary).objectForKey(CBAdvertisementDataLocalNameKey) as! NSString

if !(nameOfDeviceFound.isEqualToString("")) {
//We found Concept2 Rower

// Stop scanning, set as the peripheral to use and establish connection
self.centralManager.stopScan()
self.rowingTagPeripheral = peripheral
self.rowingTagPeripheral.delegate = self
self.centralManager.connectPeripheral(peripheral, options: nil)
} else {
self.deviceConnectedLabel.text = "Rameur Concept2 non trouvé"
}
}

Where did I make the mistake ?
Thank you very much for your help

dxpack
Paddler
Posts: 13
Joined: July 5th, 2015, 1:59 am

Re: iOS Development

Post by dxpack » July 7th, 2015, 6:45 am

You'll want to instantiate a CBCentral and save it to an ivar (you may already have this):

Code: Select all


class YourClass: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
    
    var central: CBCentral!
    var peripheral: CBPeripheral!

    override init() {
        central = CBCentral(delegate: self, queue: nil, options: nil)
    }

Then later in YourClass, you'll want to implement the centralManagerDidUpdateState(central:) function, which is called once you instantiated the CBCentral in init:

Code: Select all


func centralManagerDidUpdateState(central: CBCentralManager!) {

    if central.state == .PoweredOn {
        central.scanForPeripheralsWithServices(["CE060000-43E5-11E4-916C-0800200C9A66"], options: nil)
    } else {
        println("Local device Bluetooth not available")
    }
     
}

You'll want to implement the centralManager(central:didDiscoverPeripheral:advertisementData:RSSI:) function, which is called if the central finds the peripheral with the UUID shown above (that's the Concept2 PM5 Base Service, specifically used for a central to connect), where you can try connecting to the peripheral:

Code: Select all


func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {

    central.stopScan()
    central.connectPeripheral(peripheral, options: nil)

}

And then you'll want to implement the centralManager(central:didConnectPeripheral:) function to connect to it:

Code: Select all


func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {

    peripheral.delegate = self
    peripheral.discoverServices(nil)

}

From there you'll have to implement the peripheral delegate functions to query for characteristics, subscribe to updates on characteristics, etc.

Piboo
Paddler
Posts: 12
Joined: April 7th, 2015, 6:33 pm

Re: iOS Development

Post by Piboo » July 7th, 2015, 7:43 am

Thank you for your rapid answer.

I already implemented the CBCentral variable in my viewDidLoad.
I did too for the centralManagerDidUpdateState.

Maybe my problem come from:

Code: Select all


 let nameOfDeviceFound:NSString = (advertisementData as NSDictionary).objectForKey(CBAdvertisementDataLocalNameKey) as! NSString
        
        if !(nameOfDeviceFound.isEqualToString("")) {

Is it useful here ?

dxpack
Paddler
Posts: 13
Joined: July 5th, 2015, 1:59 am

Re: iOS Development

Post by dxpack » July 7th, 2015, 4:13 pm

I don't know anything about using advertisementData to identify a peripheral. The UUID I posted (and robnotyou posted) is specifically noted in the C2 documentation as the service UUID used to identify a PM5 peripheral. I suggest using it.

Just to note, with Swift you usually want to avoid as! whenever possible. And avoid casting to ObjC objects whenever possible. as! means you're guaranteeing to the compiler that the value will be what you claim it will be. If during runtime it isn't, your application will crash. Unnecessarily casting to ObjC (dictionaries, strings, etc) just brings along a bunch of baggage for no useful reason.

For example, this:

Code: Select all


let nameOfDeviceFound:NSString = (advertisementData as NSDictionary).objectForKey(CBAdvertisementDataLocalNameKey) as! NSString

if !(nameOfDeviceFound.isEqualToString("")) {
    //We found Concept2 Rower
}

Is better written in Swift as:

Code: Select all


if let nameOfDeviceFound = advertisementData[CBAdvertisementDataLocalNameKey] as? String where nameOfDeviceFound != "" {
    // We found a non-empty string
}


horseshoe7
Paddler
Posts: 15
Joined: April 21st, 2015, 6:28 am

Re: iOS Development

Post by horseshoe7 » July 9th, 2015, 1:57 am

I'm a bit further along than Piboo and doing my implementation in Objective-C.

I'm currently parsing the data coming in from the rowing service and don't know how to interpret the bytes.

The spec says (for example UUID 0x0037),
Data bytes packed as follows:
Elapsed Time Lo (0.01 sec lsb),
Elapsed Time Mid,
Elapsed Time High,
Distance Lo (0.1 m lsb),
Distance Mid,
Distance High,
... etc.
Which means what? I wrote this code, but to be honest I'm not sure how to interpret these bytes.

Code: Select all

    const uint8_t *payload = [data bytes];  // data is the characteristic.value
    
    NSTimeInterval secondsFraction = 0.01 * (uint8_t)payload[0];  // so the byte, 0-255, is multiplied by 0.01 and that's how many seconds??  Should it then have a value of 0-99 ?
    NSTimeInterval seconds = (NSTimeInterval)1.f * (uint8_t)payload[1];  // likewise, this value would be 0-59 ?
    NSTimeInterval minutes = (NSTimeInterval)1.f * (uint8_t)payload[2];  // what if I am rowing for longer than 255 minutes?  See, I think I don't understand...

horseshoe7
Paddler
Posts: 15
Joined: April 21st, 2015, 6:28 am

Re: iOS Development

Post by horseshoe7 » July 9th, 2015, 5:33 am

Allow me to answer my own question, may it be of help to you:
horseshoe7 wrote: Data bytes packed as follows:
Elapsed Time Lo (0.01 sec lsb),
Elapsed Time Mid,
Elapsed Time High,
Distance Lo (0.1 m lsb),
Distance Mid,
Distance High,
... etc.
For these two properties above, you have to use the 3 bytes as one value, like this:

Code: Select all



uint32_t decimetres;  // because the LSB is 0.1m, this means the 32-bit unsigned int value is counting decimetres.
[data getBytes:&decimetres range:NSMakeRange(3, 3)];
NSLog(@"Distance in m: %.1f", (float)decimetres/10.f);

Piboo
Paddler
Posts: 12
Joined: April 7th, 2015, 6:33 pm

Re: iOS Development

Post by Piboo » July 11th, 2015, 3:02 am

Sorry for the delay. Lot of work these last days.

I will test it today and tell you.
As u said I don't know why with

Code: Select all

 CBAdvertisementDataLocalNameKey
it's not working.
I will use your method.

Concerning get back the data I already write it in another class RowerTag like this (not tested):

Code: Select all

    class func dataToUnsignedBytes32(value:NSData) -> [UInt32]{
        let count = value.length
        var array = [UInt32](count: count, repeatedValue: 0)
        value.getBytes(&array, length: count * sizeof(UInt32))
        
        return array
    }


    // Distance 0.1m
    class func getDistance(value:NSData) -> Double {
        //let range:NSRange = NSMakeRange(0, 3)
        let dataFromRowing = dataToUnsignedBytes32(value)
        let distance:Double = Double(dataFromRowing[5]) * 65536 + Double(dataFromRowing[4]) * 256 + Double(dataFromRowing[3])
        
        return distance
    }


But I'm doing a conversion to get the value
So to build the distance I have to get the 3 first three value (Lo, Mid and Hi) of the return array where each is a UInt8 type.

Do I have to calculate like this :
distance = Hi *65536 + Mid * 255 + Lo ?

As we get bytes we need to convert it in bit no ?

Thank you for your help :)

Piboo
Paddler
Posts: 12
Joined: April 7th, 2015, 6:33 pm

Re: iOS Development

Post by Piboo » July 11th, 2015, 3:04 am

Why did divide by 10 the distance ?
It's said that is 0.1m so it's already in meter no ?

Locked