Android Question BLE - Candence/Speed Sensor

juve021

Member
Licensed User
Hi, I'm working on an app to interface to a set of Bluetooth Cadence/Speed (cycling) sensors. Using your (Erel) BLE - Heartrate Monitor, I've successfully connected to the sensors and can read data via log. My issue is actually getting readable data out.
The sensor are using the standard format CSC MEASUREMENT format from BLE spec which states:
https://www.bluetooth.com/specifica....bluetooth.characteristic.csc_measurement.xml

Basically if I have the characteristics, how do I do bit manipulations to pull say "UINT32" or "UINT16"?

Basically I'm trying to pull the "Cumulative Wheel Revolutions", "Last Wheel Event Time" and "Cumulative Crank Revolutions" of the spec from my pulled data

Here is the pertinent sub:
B4X:
Private Sub Manager_DataAvailable (ServiceId As String, Characteristics As Map)
    If firstRead Then
        firstRead = False
        manager.SetNotify(SC_SERVICE, CSC_MEASUREMENT, True)
        Return
    End If
   
    'this is where to get data and convert to readable info
    Dim b2() As Byte = Characteristics.Get(CSC_MEASUREMENT)
    Log("Data: " & b2)
End Sub
 
Last edited:

emexes

Well-Known Member
Licensed User
Firstly, the b2() can (for some people) be easier to read as hex, so:

include ByteConverter library (which you'll need anyway to convert between bytes, shorts and ints), then:

B4X:
Dim bc as ByteConverter

Dim b2() As Byte = Characteristics.Get(CSC_MEASUREMENT)
Log("Data: " & bc.HexFromBytes(b2))
Each byte is two hexadecimal digits (0..9 A..F)

Next, you mentioned bit manipulations: those are under Bit core class, eg:

B4X:
If Bit.And(X, 0x80) <> 0 Then    'if bit 7 set then
but I think you're more looking for other conversion functions of the ByteConverter library, eg:

to convert 8 bytes to two 32-bit ints (which are signed, but we'll ignore that little hurdle for a moment):

B4X:
Dim bc As ByteConverter

Dim b2() As Byte = Array As Byte (0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x,21, 0x22)    'sample data
Dim ConvertedData() As Int = bc.IntsFromBytes(b2)

Log(ConvertedData)
Log(Bit.ToHexString(ConvertedData))
The ByteConverter and Bit autocomplete/help should get you to the finish line. If you run into problems extracting data from the BLE data in a byte array, then post the data as a hex string and describe which part of that data you want to extract (eg, bytes 3-to-6, with first byte numbered as 1), and as what (eg, signed/unsigned, 8/16/24/32 bits).
 

emexes

Well-Known Member
Licensed User
Now that I've had my initial dose of caffeine for the day, I had a look at the Bluetooth characteristic documentation page you pointed to, and I think I can imagine where you're getting tripped up. No problem, give me a few minutes.
 

juve021

Member
Licensed User
Now that I've had my initial dose of caffeine for the day, I had a look at the Bluetooth characteristic documentation page you pointed to, and I think I can imagine where you're getting tripped up. No problem, give me a few minutes.
Thanks for looking at it.

Yes I was able to output the bytes in hex but that didn't really simplify things for me.
 

emexes

Well-Known Member
Licensed User
Sorry, got distracted by Saturday morning cleanup. Try something like this (done in B4J but same code should work in B4A too)
B4X:
'get 16-bit value from bytes B(I)..B(I+1)
Sub GetShortFromWithinByteArray(B() As Byte, I As Int) As Short
 
    If B.Length < I + 2 Then    'pessimists of the world, unite!
        Log("wtf " & " " & B.Length & " " & I)
    End If
 
    Dim bc As ByteConverter
    bc.LittleEndian = True    'BLE GATT generally does things the right way ;-)
 
    Dim ByteArrayIn() As Byte = Array As Byte(B(I), B(I+1))
 
    Dim ShortArrayOut() As Short = bc.ShortsFromBytes(ByteArrayIn)
 
    Return ShortArrayOut(0)
 
    'if your eyesight is bulletproof, then you could condense the above three lines to:
    'Return bc.ShortsFromBytes(Array As Byte(B(I), B(I+1)))(0)
 
End Sub

'get 32-bit value from bytes B(I)..B(I+3)
Sub GetIntFromWithinByteArray(B() As Byte, I As Int) As Int
 
    If B.Length < I + 4 Then    'pessimists of the world, unite!
        Log("wtf " & " " & B.Length & " " & I)     
    End If
 
    Dim bc As ByteConverter
    bc.LittleEndian = True
 
    Dim ByteArrayIn() As Byte = Array As Byte(B(I), B(I+1), B(I+2), B(I+3))
 
    Dim IntArrayOut() As Int = bc.IntsFromBytes(ByteArrayIn)
 
    Return IntArrayOut(0)
 
End Sub

Sub HandleWheelPacket(B() As Byte)
 
    If B.Length = 0 Then
        'empty packet = no data
        Return
    End If
 
    Dim GetDataFrom As Int = 0    'start at beginning of packet
 
    Dim DataFlags As Byte = B(GetDataFrom)
    GetDataFrom = GetDataFrom + 1    'advance to next field
 
    Dim WheelDataFlag As Boolean = Bit.And(DataFlags, Bit.ShiftLeft(1,0)) <> 0    'bit 0
    Dim CrankDataFlag As Boolean = Bit.And(DataFlags, Bit.ShiftLeft(1,1)) <> 0    'bit 1
 
    'check that we actually have that data
 
    Dim NumDataBytesExpected As Int = 0
    If WheelDataFlag Then NumDataBytesExpected = NumDataBytesExpected + 6
    If CrankDataFlag Then NumDataBytesExpected = NumDataBytesExpected + 4
 
    If (B.Length - GetDataFrom) < NumDataBytesExpected Then    'if numbytesremaining < numbytesexpected
        Log("wtf missing data")
        Return
    End If
     
    If WheelDataFlag Then
        Dim CumulativeWheelRevolutions As Int = GetIntFromWithinByteArray(B, GetDataFrom)
        GetDataFrom = GetDataFrom + 4    'advance to next field
     
        Dim LastWheelEventTime As Short = GetShortFromWithinByteArray(B, GetDataFrom)
        GetDataFrom = GetDataFrom + 2    'advance to next field
    End If
 
    If CrankDataFlag Then
        Dim CumulativeCrankRevolutions As Short = GetShortFromWithinByteArray(B, GetDataFrom)
        GetDataFrom = GetDataFrom + 2    'next field
     
        Dim LastCrankEventTime As Short = GetShortFromWithinByteArray(B, GetDataFrom)
        GetDataFrom = GetDataFrom + 2    'next field
    End If
 
    'process above-extracted data
 
    Dim bc As ByteConverter
 
    Log("Packet is " & bc.HexFromBytes(B))
 
    If WheelDataFlag Then
        Log("Wheel " & CumulativeWheelRevolutions & " " & LastWheelEventTime)
    End If
 
    If CrankDataFlag Then
        Log("Crank " & CumulativeCrankRevolutions & " " & LastCrankEventTime)
    End If
 
End Sub

Sub AppStart (Args() As String)

    Log("Test full packet")
    Dim B() As Byte = Array As Byte(0x03, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x33, 0x00, 0x44, 0x00)
    HandleWheelPacket(B)
 
    Log("Test wheel packet with superfluous data")
    Dim B() As Byte = Array As Byte(0x01, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x33, 0x00, 0x44, 0x00)
    HandleWheelPacket(B)
 
    Log("Test crank packet")
    Dim B() As Byte = Array As Byte(0x02, 0x33, 0x00, 0x44, 0x00)
    HandleWheelPacket(B)
 
    Log("Test empty packet (!) with superfluous data")
    Dim B() As Byte = Array As Byte(0x00, 0x33, 0x00, 0x44, 0x00)
    HandleWheelPacket(B)
 
    Log("Test empty packet (!)")
    Dim B() As Byte = Array As Byte(0x00)
    HandleWheelPacket(B)
 
    Log("Test short packet")
    Dim B() As Byte = Array As Byte(0x02, 0x33, 0x00, 0x44)
    HandleWheelPacket(B)
 
    Log("Finito")
 
End Sub
Log output of test run is:
B4X:
Test full packet
Packet is 0311000000220033004400
Wheel 17 34
Crank 51 68
Test wheel packet with superfluous data
Packet is 0111000000220033004400
Wheel 17 34
Test crank packet
Packet is 0233004400
Crank 51 68
Test empty packet (!) with superfluous data
Packet is 0033004400
Test empty packet (!)
Packet is 00
Test short packet
wtf missing data
Finito
which is terse but looks correct.
 

emexes

Well-Known Member
Licensed User
Two minor things:

1/ previous post had an off-by-one error (now fixed) on the "pessimists of the world" lines, but... if your packets are complete, it won't have caused a problem

2/ the wheel revolutions is a 32-bit number, whereas the crank revolutions is a 16 bit number

2a/ you will *never* get to the 32-bit limit (worst case: 2 billion revolutions of a 1 m diameter wheel = 6 billion metres = 6 million km = 60000 hours at 100 km/h = more than 6 years of 24/7 riding, or several human lifetimes at more realistic riding rates (or 114 years at Tour de France rates)

2b/ you *will* probably get to the 16-bit limit (32767 or 65535 revolutions), and some riders might even hit it within a day.
 

juve021

Member
Licensed User
@emexes YOU ARE DA MAN!

Your code definitely seems to work. Here is my output.
B4X:
Installing file.
** Activity (main) Pause, UserClosed = false **
PackageAdded: package:caschy.cycle
** Service (starter) Create **
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
1816
2a5b
** Activity (main) Resume **
StateChanged: 12
****************
Name: Wahoo CADENCE D6E9
DeviceId: C6:D4:12:EC:E9:C4
Connecting to: C6:D4:12:EC:E9:C4
Discovering services.
Connected
Setting descriptor. Success = true
writing descriptor: true
Test full packet
Packet is 0202003210
Crank 2 4146
Test full packet
Packet is 0203008713
Crank 3 4999
Test full packet
Packet is 0203008713
Crank 3 4999
Test full packet
Packet is 020400B116
Crank 4 5809
Test full packet
Packet is 020600D81C
Crank 6 7384
Test full packet
Packet is 020700F11F
Crank 7 8177
Test full packet
Packet is 0208000523
Crank 8 8965
Test full packet
Packet is 0208000523
Crank 8 8965
Test full packet
Packet is 0209001726
Crank 9 9751
Test full packet
Packet is 020B00642C
Crank 11 11364
Test full packet
Packet is 020B00642C
Crank 11 11364
Test full packet
Packet is 020C005E30
Crank 12 12382
Test full packet
Packet is 020C005E30
Crank 12 12382
Test full packet
Packet is 020D007335
Crank 13 13683
Test full packet
Packet is 020E00CD39
Crank 14 14797
Test full packet
Packet is 020F00CA3D
Crank 15 15818
Test full packet
Packet is 021000B041
Crank 16 16816
Test full packet
Packet is 021000B041
Crank 16 16816
Test full packet
Packet is 0211009345
Crank 17 17811
Test full packet
Packet is 021200644A
Crank 18 19044
Test full packet
Packet is 021200644A
Crank 18 19044
Test full packet
Packet is 0213003551
Crank 19 20789
Test full packet
Packet is 0213003551
Crank 19 20789
Test full packet
Packet is 0214005556
Crank 20 22101
Test full packet
Packet is 0215007D5A
Crank 21 23165
Test full packet
Packet is 021600115E
Crank 22 24081
Test full packet
Packet is 0218006C64
Crank 24 25708
Test full packet
Packet is 0218006C64
Crank 24 25708
Test full packet
Packet is 0219006867
Crank 25 26472
Test full packet
Packet is 021B00296D
Crank 27 27945
Test full packet
Packet is 021C000E70
Crank 28 28686
Test full packet
Packet is 021D00F072
Crank 29 29424
Test full packet
Packet is 021D00F072
Crank 29 29424
Test full packet
Packet is 021F00D978
Crank 31 30937
Test full packet
Packet is 022000D57B
Crank 32 31701
Test full packet
Packet is 022100D37E
Crank 33 32467
Test full packet
Packet is 022300D184
Crank 35 -31535
Test full packet
Packet is 022300D184
Crank 35 -31535
Test full packet
Packet is 022400D087
Crank 36 -30768
Test full packet
Packet is 022500CF8A
Crank 37 -30001
Test full packet
Packet is 022700C590
Crank 39 -28475
Test full packet
Packet is 022800B893
Crank 40 -27720
Test full packet
Packet is 022800B893
Crank 40 -27720
Test full packet
Packet is 022900A996
Crank 41 -26967
Test full packet
Packet is 022B00899C
Crank 43 -25463
Test full packet
Packet is 022C00819F
Crank 44 -24703
Test full packet
Packet is 022D0075A2
Crank 45 -23947
Test full packet
Packet is 022D0075A2
Crank 45 -23947
Test full packet
Packet is 022F0070A8
Crank 47 -22416
Test full packet
Packet is 0230006CAB
Crank 48 -21652
Test full packet
Packet is 02310062AE
Crank 49 -20894
Test full packet
Packet is 02330052B4
Crank 51 -19374
Test full packet
Packet is 02330052B4
Crank 51 -19374
Test full packet
Packet is 0234004CB7
Crank 52 -18612
Test full packet
Packet is 02350041BA
Crank 53 -17855
Test full packet
Packet is 0237002CC0
Crank 55 -16340
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 02380048C3
Crank 56 -15544
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
1816
2a5b
** Activity (main) Resume **
StateChanged: 12
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
Test full packet
Packet is 023D00F0D9
Crank 61 -9744
** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
1816
2a5b
** Activity (main) Resume **
StateChanged: 12
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
****************
Name: Wahoo SPEED 6B1B
DeviceId: E7:A1:7C:E6:AF:4D
Connecting to: E7:A1:7C:E6:AF:4D
****************
Name: Wahoo SPEED 6B1B
DeviceId: E7:A1:7C:E6:AF:4D
Connecting to: E7:A1:7C:E6:AF:4D
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
1816
2a5b
** Activity (main) Resume **
StateChanged: 12
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Discovering services.
Discovering services.
** Activity (main) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
1816
2a5b
** Activity (main) Resume **
StateChanged: 12
Connected
Connected
Test full packet
Packet is 0242002004
Crank 66 1056
Setting descriptor. Success = true
writing descriptor: true
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 018D02000003B8
Wheel 653 -18429
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0193020000F6BC
Wheel 659 -17162
Test full packet
Packet is 01980200000FC1
Wheel 664 -16113
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 019D0200000AC5
Wheel 669 -15094
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 01A302000029C9
Wheel 675 -14039
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 01AA02000079CD
Wheel 682 -12935
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 01B10200009CD1
Wheel 689 -11876
Test full packet
Packet is 024E00E04F
Crank 78 20448
Test full packet
Packet is 01BA02000040D6
Wheel 698 -10688
Test full packet
Packet is 024E00E04F
Crank 78 20448
Test full packet
Packet is 024E00E04F
Crank 78 20448
Test full packet
Packet is 01C302000095DA
Wheel 707 -9579
Test full packet
Packet is 024F005455
Crank 79 21844
Test full packet
Packet is 01C902000057DD
Wheel 713 -8873
Test full packet
Packet is 024F005455
Crank 79 21844
Test full packet
Packet is 01D3020000C9E1
Wheel 723 -7735
Test full packet
Packet is 025000755A
Crank 80 23157
Test full packet
Packet is 01DB020000A6E5
Wheel 731 -6746
Test full packet
Packet is 0251005D5F
Crank 81 24413
Test full packet
Packet is 0251005D5F
Crank 81 24413
Test full packet
Packet is 01E002000040EA
Wheel 736 -5568
Test full packet
Packet is 0252001964
Crank 82 25625
Test full packet
Packet is 01E4020000E6EE
Wheel 740 -4378
Test full packet
Packet is 025300BF68
Crank 83 26815
Test full packet
Packet is 01E70200008FF2
Wheel 743 -3441
Test full packet
Packet is 01EA02000048F6
Wheel 746 -2488
Test full packet
Packet is 0254005A6D
Crank 84 27994
Test full packet
Packet is 025500EF71
Crank 85 29167
Test full packet
Packet is 01ED02000013FA
Wheel 749 -1517
Test full packet
Packet is 025500EF71
Crank 85 29167
Test full packet
Packet is 025500EF71
Crank 85 29167
Test full packet
Packet is 01F0020000EBFD
Wheel 752 -533
Test full packet
Packet is 0256009176
Crank 86 30353
Test full packet
Packet is 01F3020000DE01
Wheel 755 478
Test full packet
Packet is 0257004A7B
Crank 87 31562
Test full packet
Packet is 0258001680
Crank 88 -32746
Test full packet
Packet is 01F6020000E705
Wheel 758 1511
Test full packet
Packet is 0258001680
Crank 88 -32746
Test full packet
Packet is 0259001485
Crank 89 -31468
Test full packet
Packet is 01F90200000E0A
Wheel 761 2574
Test full packet
Packet is 0259001485
Crank 89 -31468
Test full packet
Packet is 01FC0200004B0E
Wheel 764 3659
Test full packet
Packet is 025A00338A
Crank 90 -30157
Test full packet
Packet is 01FF020000A212
Wheel 767 4770
Test full packet
Packet is 025B00828F
Crank 91 -28798
Test full packet
Packet is 01020300001B17
Wheel 770 5915
Test full packet
Packet is 025B00828F
Crank 91 -28798
Test full packet
Packet is 025C000995
Crank 92 -27383
Test full packet
Packet is 01040300002A1A
Wheel 772 6698
Test full packet
Packet is 025C000995
Crank 92 -27383
Test full packet
Packet is 0106030000471D
Wheel 774 7495
Test full packet
Packet is 025D00C49A
Crank 93 -25916
Test full packet
Packet is 01090300001C22
Wheel 777 8732
Test full packet
Packet is 025E00C2A0
Crank 94 -24382
Test full packet
Packet is 010B0300006E25
Wheel 779 9582
Test full packet
Packet is 025E00C2A0
Crank 94 -24382
Test full packet
Packet is 025E00C2A0
Crank 94 -24382
Test full packet
Packet is 010D030000D428
Wheel 781 10452
Test full packet
Packet is 025F0008A7
Crank 95 -22776
Test full packet
Packet is 0110030000102E
Wheel 784 11792
Test full packet
Packet is 025F0008A7
Crank 95 -22776
Test full packet
Packet is 026000A3AD
Crank 96 -21085
Test full packet
Packet is 01130300007D33
Wheel 787 13181
Test full packet
Packet is 01150300003E37
Wheel 789 14142
Test full packet
Packet is 026000A3AD
Crank 96 -21085
Test full packet
Packet is 01160300002A39
Wheel 790 14634
Test full packet
Packet is 026000A3AD
Crank 96 -21085
Test full packet
Packet is 026100A0B4
Crank 97 -19296
Test full packet
Packet is 01180300001E3D
Wheel 792 15646
Test full packet
Packet is 026100A0B4
Crank 97 -19296
Test full packet
Packet is 011A0300003741
Wheel 794 16695
Test full packet
Packet is 0262000EBC
Crank 98 -17394
Test full packet
Packet is 011C0300007945
Wheel 796 17785
Test full packet
Packet is 0262000EBC
Crank 98 -17394
Test full packet
Packet is 0262000EBC
Crank 98 -17394
Test full packet
Packet is 011E030000DA49
Wheel 798 18906
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0120030000674E
Wheel 800 20071
Test full packet
Packet is 01220300002B53
Wheel 802 21291
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0123030000A855
Wheel 803 21928
Test full packet
Packet is 01240300003358
Wheel 804 22579
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0126030000905D
Wheel 806 23952
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 01280300003463
Wheel 808 25396
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 01290300001F66
Wheel 809 26143
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 012A0300002E69
Wheel 810 26926
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 012B030000656C
Wheel 811 27749
Test full packet
Packet is 012C030000C26F
Wheel 812 28610
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 012E030000D476
Wheel 814 30420
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 012F030000A27A
Wheel 815 31394
Test full packet
Packet is 012F030000A27A
Wheel 815 31394
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0130030000BF7E
Wheel 816 32447
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 01320300009B87
Wheel 818 -30821
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 01320300009B87
Wheel 818 -30821
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0133030000495F
Wheel 819 24393
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 0133030000495F
Wheel 819 24393
Test full packet
Packet is 0133030000495F
Wheel 819 24393
Test full packet
Packet is 02630025C4
Crank 99 -15323
Test full packet
Packet is 02630025C4
Crank 99 -15323
Now I just have to figure out how to calculate cadence from the crank, speed from the wheel and maybe power output
 

emexes

Well-Known Member
Licensed User
The immediately-obvious problem is that Einstein here failed to consider that those unsigned timer values are going to hit negative territory one heck of a lot quicker than the revolution counters.

But no problem with the math, if you store them and add/subtract them as Ints, and Bit.And them with 0xFFFFF before using the results.

eg, Crank 33 = 32467, Crank 35 = -31535
to find out the time between those two events = timestamp(Crank 34) - timestamp(Crank 33) = -31535 - 32467 = -64001
which is negative and thus clearly not correct unless you're doing laps in the TARDIS or similar, but if do what magic we can do, ie
Bit.And(-64001, 0xFFFFF) = 1535 ticks, then things look more plausible.

To work out the current speeds, using say this data:
B4X:
Packet is 019D0200000AC5
Wheel 669 -15094
Test full packet
Packet is 0242002004
Crank 66 1056
Test full packet
Packet is 01A302000029C9
Wheel 675 -14039
Test full packet
Packet is 0242002004
Crank 66 1056
you'd do something like the following, and then you could even work out what gear the bike's in by the ratio between the Wheel and Crank speeds (RevsPerSecond).

Power will be more difficult, though, because you'll need to account for road gradient, air resistance and acceleration/deceleration, which in turn require values for frontal area, coefficient of drag and mass. Unless you've got a load cell on your pedal or something (which probably sounds like a joke, but... we had a vehicle brake pedal load cell to measure how hard the driver was pressing the brake pedal, and that was 20 years ago).

B4X:
Sub Globals
    Dim LastWheelRevs As Int
    Dim LastWheelTicks As Int
    Dim LastCrankRevs As Int
    Dim LastCrankTicks As Int

    Dim WheelRevsPerSecond As Float
    Dim WheelKilometresPerHour As Float
    Dim WheelSpeedValidFlag As Boolean = False

    Dim CrankRevsPerSecond As Float
    Dim CrankSpeedValidFlag As Boolean = False
End Sub

Sub HandleWheelData(Revs As Int, Ticks As Short)

    Dim ThisWheelRevs As Int = Revs
    Dim ThisWheelTicks As Int = Ticks

    Dim RevsSinceLast As Int = Bit.And(ThisWheelRevs - LastWheelRevs, 0xFFFFF)
    Dim TicksSinceLast As Int= Bit.And(ThisWheelTicks - LastWheelTicks, 0xFFFFF)

    WheelSpeedValidFlag = False    'pessimists of the world, unite!
    If RevsSinceLast >= 1 and RevsSinceLast <= 5 Then
        Dim TicksPerRev As Float = Abs(TicksSinceLast) / RevsSinceLast    'Abs() to force floating-point divide (rather than integer divide)
        If TicksPerRev >= 100 and TicksPerRev <= 10000 Then    '10 rev/sec to 0.1 rev/sec
            WheelRevsPerSecond = 1000 / TicksPerRev    'assuming ticks are milliseconds
            Dim WheelMetresPerSecond As Float = WheelRevsPerSecond * (WheelDiameterMillimeters / 1000) * cPI
            WheelKilometresPerHour = WheelMetresPerSecond * 3600 / 1000
            WheelSpeedValidFlag = True
        End If
    End If

    LastWheelRevs = ThisWheelRevs
    LastWheelTicks = ThisWheelTicks

End Sub

Sub HandleCrankData(Revs As Int, Ticks As Short)

    Dim ThisCrankRevs As Int = Revs
    Dim ThisCranklTicks As Int = Ticks

    Dim RevsSinceLast As Int = Bit.And(ThisCrankRevs - LastCrankRevs, 0xFFFFF)
    Dim TicksSinceLast As Int= Bit.And(ThisCrankTicks - LastCrankTicks, 0xFFFFF)

    CrankSpeedValidFlag = False    'pessimists of the world, unite!
    If RevsSinceLast >= 1 and RevsSinceLast <= 5 Then
        Dim TicksPerRev As Float = Abs(TicksSinceLast) / RevsSinceLast    'Abs() to force floating-point divide (rather than integer divide)
        If TicksPerRev >= 100 and TicksPerRev <= 10000 Then    '10 rev/sec to 0.1 rev/sec
            CrankRevsPerSecond = 1000 / TicksPerRev    'assuming ticks are milliseconds
            CrankSpeedValidFlag = True
        End If
    End If

    LastCrankRevs = ThisCrankRevs
    LastCrankTicks = ThisCrankTicks

End Sub
 

emexes

Well-Known Member
Licensed User
You'd probably get a more accurate power measurement by measuring the rider's temperature vs the ambient temperature (and possibly humidity), and creating a table that converted those two or three readings to power. Not quite sure where you'd site the rider thermometer, but I'd love to be the one who introduces this system to riders, says, "right... now, to stay competitive we'll be needing to measure your body temperature, so we've created this Bluetooth sensor that should do the job" and then show them something resembling a buttplug, see how long we can keep them believing ;-)

Although core body temperature will probably be too stable, I think you'd be looking more for temperature about the chest or armpits. Or maybe electrical resistance across a distance of skin, which should vary with perspiration, which should vary with heat elimination, which should vary with exertion/power.
 

juve021

Member
Licensed User
@emexes, this is amazing! Thank you so much. FYI, this is for a stationary/indoor bike so I think that makes the power calc easier. I was just thinking on the lines of your table that just interpolates values. Found this table correlating speed in kph to power.

B4X:
0, 0
5.05, 32.37
9.99, 64.73
15.04, 100.50
20.09, 137.11
25.03, 177.97
29.98, 220.54
34.98, 269.05
40.07, 322.66
45.02, 383.92
50.01, 451.13
54.96, 525.99
60.05, 607.65
 

emexes

Well-Known Member
Licensed User
@emexesFound this table correlating speed in kph to power.
I was wondering how they determined power without knowing force, then realised it is probably a fan or similar where the force varies with square of speed, thus power varies with cube of speed, thus you can replace the table-and-interpolation with a cubic polynomial:

Power = 0.0009925 * Speed * Speed * Speed + 0.003019 * Speed * Speed + 6.377 * Speed

{ or Power = ((0.0009925 * Speed + 0.003019) * Speed + 6.377) * Speed }

I feel some loss at not being able to present the body-temperature plan to bicycle riders, though :-/
 

juve021

Member
Licensed User
I was wondering how they determined power without knowing force, then realised it is probably a fan or similar where the force varies with square of speed, thus power varies with cube of speed, thus you can replace the table-and-interpolation with a cubic polynomial:

Power = 0.0009925 * Speed * Speed * Speed + 0.003019 * Speed * Speed + 6.377 * Speed

{ or Power = ((0.0009925 * Speed + 0.003019) * Speed + 6.377) * Speed }

I feel some loss at not being able to present the body-temperature plan to bicycle riders, though :-/
Never thought about it this way. Wow, that is pretty impressive.

I really can't thank you enough for all of your time.

I am uploading the new code and will post output in a few.
 

juve021

Member
Licensed User
OK here is some output, looks decent to me. Just have to test it out on an actual ride I know values to compare. I added KPH and Cadence. How do I format to 1 or 2 decimals?

B4X:
StateChanged: 12
Wheel KPH:9.358932495117188
Wheel KPH:12.42983341217041
Wheel KPH:14.830240249633789
Cranks/Minute:55.350555419921875
Wheel KPH:16.990612030029297
Cranks/Minute:58.70841598510742
Wheel KPH:18.417844772338867
Cranks/Minute:60.54490280151367
Wheel KPH:19.586402893066406
Cranks/Minute:65.57377624511719
Wheel KPH:20.71085548400879
Cranks/Minute:72.5513916015625
Cranks/Minute:76.04562377929688
Wheel KPH:22.192317962646484
Cranks/Minute:77.97270965576172
Wheel KPH:22.481782913208008
Cranks/Minute:78.125
Wheel KPH:21.612581253051758
Cranks/Minute:78.125
Wheel KPH:16.898073196411133
Cranks/Minute:78.125
Cranks/Minute:78.125
Wheel KPH:15.764664649963379
Wheel KPH:17.380874633789063
Cranks/Minute:78.125
Wheel KPH:17.424802780151367
Cranks/Minute:77.51937866210938
Wheel KPH:15.481467247009277
Cranks/Minute:77.12081909179688
Wheel KPH:14.71767520904541
Cranks/Minute:77.61966705322266
Wheel KPH:14.297171592712402
Cranks/Minute:77.12081909179688
Wheel KPH:15.861380577087402
Wheel KPH:16.520160675048828
Cranks/Minute:76.38446807861328
Cranks/Minute:75.47169494628906
Wheel KPH:14.944539070129395
Cranks/Minute:75.28231048583984
Wheel KPH:13.13499641418457
Cranks/Minute:74.58049011230469
Wheel KPH:12.565759658813477
Cranks/Minute:74.90636444091797
Wheel KPH:12.601486206054688
Cranks/Minute:75.75757598876953
Cranks/Minute:75.37688446044922
Wheel KPH:12.474813461303711
Cranks/Minute:75.1408920288086
Wheel KPH:12.449783325195313
Cranks/Minute:72.90400695800781
Wheel KPH:12.591259002685547
Cranks/Minute:71.85628509521484
Wheel KPH:11.941823959350586
Cranks/Minute:70.67137908935547
Wheel KPH:14.310359954833984
Cranks/Minute:68.80733489990234
Wheel KPH:14.147223472595215
Cranks/Minute:66.88963317871094
Wheel KPH:13.850384712219238
Cranks/Minute:65.00541687011719
Wheel KPH:13.55981731414795
Cranks/Minute:64.86486053466797
Wheel KPH:13.453972816467285
Cranks/Minute:64.23983001708984
Cranks/Minute:63.89776611328125
Wheel KPH:13.384322166442871
Wheel KPH:13.338290214538574
Cranks/Minute:63.7619514465332
Wheel KPH:13.241510391235352
Cranks/Minute:63.15789794921875
Wheel KPH:13.123884201049805
Cranks/Minute:63.357975006103516
Wheel KPH:13.395881652832031
Cranks/Minute:62.959075927734375
Cranks/Minute:63.291141510009766
Wheel KPH:15.959291458129883
Wheel KPH:15.328487396240234
Cranks/Minute:62.89308166503906
Wheel KPH:13.51845932006836
Cranks/Minute:62.695926666259766
Cranks/Minute:63.15789794921875
Wheel KPH:14.703725814819336
Cranks/Minute:62.82722854614258
Cranks/Minute:61.16207504272461
Wheel KPH:17.250410079956055
Wheel KPH:18.20707893371582
Cranks/Minute:59.88024139404297
Wheel KPH:18.336204528808594
Cranks/Minute:56.98005676269531
Wheel KPH:17.66791534423828
Cranks/Minute:54.44646072387695
Wheel KPH:16.934967041015625
Wheel KPH:16.29884910583496
Cranks/Minute:51.41387939453125
Cranks/Minute:46.911651611328125
Wheel KPH:15.496933937072754
Wheel KPH:14.343440055847168
Wheel KPH:13.338290214538574
Cranks/Minute:41.350791931152344
Wheel KPH:12.777949333190918
Wheel KPH:12.611732482910156
Cranks/Minute:39.76143264770508
Cranks/Minute:38.61003875732422
Wheel KPH:11.983338356018066
Wheel KPH:11.997239112854004
Wheel KPH:11.914308547973633
Cranks/Minute:37.854888916015625
Wheel KPH:11.516280174255371
Wheel KPH:11.659098625183105
Cranks/Minute:36.9458122253418
Wheel KPH:11.339495658874512
Cranks/Minute:36.4963493347168
Wheel KPH:11.176103591918945
Wheel KPH:11.439846992492676
Cranks/Minute:33.37041091918945
Wheel KPH:11.143987655639648
Wheel KPH:10.467227935791016
Wheel KPH:9.453035354614258
** Activity (main) Pause, UserClosed = true **
 

emexes

Well-Known Member
Licensed User
Better yet, use NumberFormat2 which lets you get rid of the groups-of-three separator (aka computer-readable numbers) eg:
B4X:
Log(NumberFormat2(12.34567, 1, 1, 1, False))    '12.3
Log(NumberFormat2(12.34567, 1, 2, 2, False))    '12.35
Log(NumberFormat2(12.34567, 1, 3, 3, False))    '12.346
and have variable decimal places after the point, plus pretty sure it gets rid of the point if no decimal fraction digits (which !$#% Excel doesn't do)

edit: well, not my day-to-day version of Excel 2000, anyway
 
Last edited:
Top