-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test Zwift Hub FTMS #7
Comments
Dear Joel, Are you 100% sure that the Zwift Hub is not connected to another client application (i.c. Zwift)? --> Shut down your PC, remove the ANT+ dongle and restart the PC.
Good luck! |
Dear Joel, I have uploaded a new version for the Client (v023) that is more robust when error states occur. It will not result in an other outcome of your previous test! |
Hello Jorghen, I redid the test with Ant+ disconnected and Bluethoot gsm switched off. Here is the result with FTMS_Client_ v022: Now I think it's good ! Joel PS : I'm waiting for the dongle, maybe tomorrow Feather nRF52 Client/Central: CPS, CSC and FTMS
|
Test with FTMS_Client_v023 : Feather nRF52 Client/Central: CPS, CSC and FTMS
|
Dear Joel,
|
Dear Joel,
|
I don't know if this can't help you? : nRF Connect, 2022-12-10
|
I found this program: for ESP32 BLE of which I did the test and here is the result of the services : ets Jun 8 2016 00:22:57 rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) Device name: Zwift Hub Service 1800 Disconnecting ... |
Dear Joel, One thing is immediately striking at my first scan:
I have only met trainers that are awake all the time, may be after 20 minutes go to sleep, but never within seconds/minutes... |
Let's give Client_FTMS version v025 a try!
Fingers crossed! |
Dear jörghen, Here are the results, You should know that the first attempt was a failure. The Zwift Hub is very temperamental to wake up. Unfortunately I didn't copy everything because the serial monitor had already been closed!!!! I'm trying to reproduce but I can't!!! Feather nRF52 Client/Central: CPS, CSC and FTMS
15:54:24.551 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00 00 00 00 28 33 ] |
Now I managed to get the full serial but I don't know how I woke up the Zwift hub. I can't reproduce every time!!!! 16:35:25.068 -> Feather nRF52 Client/Central: CPS, CSC and FTMS |
Did you feel resistance change when moving the pedals?
Op zo 11 dec. 2022 16:41 schreef le-joebar ***@***.***>:
… Now is OK !
16:35:25.068 -> Feather nRF52 Client/Central: CPS, CSC and FTMS
16:35:25.068 -> ----------------- Version 02.5 ------------------
16:35:25.068 -> Initialise the Bluefruit nRF52 module: Client (Central)
16:35:25.071 -> FTMS and Chars 'initialized'
16:35:25.071 -> CPS and Chars 'initialized'
16:35:25.071 -> CSCS and Chars 'initialized'
16:35:25.071 -> GA and Chars 'initialized'
16:35:25.071 -> DIS and Chars 'initialized'
16:35:25.071 -> Start Scanning for CPS, CSC and FTMS!
16:35:25.071 -> Found advertising Peripheral with FTMS service!, see the
Raw Data packet:
16:35:25.071 -> Timestamp MAC Address Rssi Data
16:35:25.071 -> 000000717 F8:9C:FC:53:5E:49 -64
09-02-16-18-26-18-18-18-0A-18
16:35:25.071 -> Feather nRF52 (Central) connected to Trainer (Peripheral)
device: [Zwift Hub] MAC Address: F8:9C:FC:53:5E:49
16:35:25.071 -> Now checking all Client Services and Characteristics!
16:35:25.071 -> If Mandatory Services Fail --> the Client will disconnect!
16:35:25.071 -> First checking Generic Access and Device Information
Services and Characteristics!
16:35:25.229 -> Found Client Generic Access
16:35:25.229 -> -> Client Reads Device Name: [Zwift Hub]
16:35:25.231 -> -> Client Reads Appearance: [1152]
16:35:25.231 -> Found Client Device Information
16:35:25.242 -> -> Client Reads Manufacturer: [Zwift]
16:35:25.242 -> -> Client Reads Model Number: [06]
16:35:25.378 -> -> Client Reads Serial Number: [06-F89CFC535E49]
16:35:25.378 -> Discovering Mandatory Client Fitness Machine (FTM) Service
... Found it! FTMS Max Payload: 20 Data Length: 27
16:35:25.425 -> Discovering Client FTM Feature Characteristic ... Found it!
16:35:25.832 -> -> Client Reads Raw FTM Feature bytes: [8] [ 00 00 00 00
00 00 00 00 ]
16:35:25.832 -> Discovering Client FTM Control Point Characteristic ...
Found it!
16:35:26.374 -> Discovering Client FTM Status Characteristic ... Found it!
16:35:26.374 -> Discovering Client FTM Training Status Characteristic ...
Ready to receive Client FTM Control Point Response Messages
16:35:26.409 -> Ready to receive Client FTM Status values
16:35:26.409 -> >>> Couldn't enable notify for Client FTM Training Status
Characteristic.
16:35:26.409 -> >>> Couldn't enable notify for Client FTM Indoor Bike Data
Characteristic.
16:35:26.409 -> >>> Couldn't enable notify for Client CP Measurement
Characteristic.
16:35:26.409 -> >>> Couldn't enable indicate for Client CP Control Point
Characteristic.
16:35:26.409 -> >>> Couldn't enable notify for Client CSC Measurement
Characteristic.
16:35:26.409 -> Client (Central) is Up and Running!
16:35:26.595 -> Not Found! Not Mandatory
16:35:26.595 -> Discovering Client FTM Supported Resistance Level Range
Characteristic ... Found it!
16:35:26.750 -> -> Client Reads Raw FTM Supported Resistance Level Range
bytes: [6] [ 00 00 64 00 01 00 ]
16:35:26.750 -> Discovering Client FTM Supported Power Range
Characteristic ... Found it!
16:35:27.058 -> -> Client Reads Raw FTM Supported Power Range bytes: [6] [
00 00 E8 03 01 00 ]
16:35:27.058 -> Discovering Client FTM Indoor Bike Data Characteristic ...
Found it!
16:35:27.291 -> Discovering Client Cycling Power (CP) Service ... Found
it! CPS Max Payload: 20 Data Length: 27
16:35:27.383 -> Discovering Client CP Measurement characteristic ... Found
it!
16:35:27.554 -> Discovering Client CP Control Point characteristic ... Not
Found! NOT Mandatory!
16:35:27.816 -> Discovering Client CP Feature characteristic ... Found it!
16:35:27.987 -> -> Client Reads Raw CP Feature bytes: [4] [ 00 00 00 00 ]
16:35:27.987 -> Discovering Client CP Sensor Location characteristic ...
NOT Found! NOT Mandatory!
16:35:27.987 -> Discovering Cycling Speed and Cadence (CSC) Service ...
Not Found! CSC Service is Not Mandatory!
16:35:28.391 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 00 00 00 00 00 00 00 00 ]
16:35:28.513 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
00 01 ]
16:35:30.519 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 00 00 00 00 00 00 00 00 ]
16:35:30.597 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
00 01 ]
16:35:32.596 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 07 00 00 00 00 00 00 00 ]
16:35:32.673 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
07 01 ]
16:35:32.673 -> -> Client Rec'd Raw FTM Machine Status Data: [1] [ 04 ]
16:35:34.658 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 22 01 28 33 00 ]
16:35:34.734 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:34.769 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 22 01 28 33 ]
16:35:36.711 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 09 00 28 33 00 ]
16:35:36.789 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:36.789 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 09 00 28 33 ]
16:35:38.772 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 00 00 28 33 00 ]
16:35:39.099 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:39.099 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 00 00 28 33 ]
16:35:40.880 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 14 00 28 33 00 ]
16:35:40.958 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:40.958 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 14 00 28 33 ]
16:35:42.973 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 22 00 28 33 00 ]
16:35:43.066 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:43.066 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 22 00 28 33 ]
16:35:45.077 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 25 00 28 33 00 ]
16:35:45.154 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:45.154 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 25 00 28 33 ]
16:35:47.144 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 23 00 28 33 00 ]
16:35:47.470 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:47.470 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 23 00 28 33 ]
16:35:49.244 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 1C 00 28 33 00 ]
16:35:49.415 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:49.415 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 1C 00 28 33 ]
16:35:51.357 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 0F 00 28 33 00 ]
16:35:51.481 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:51.481 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 0F 00 28 33 ]
16:35:53.451 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 00 00 28 33 00 ]
16:35:53.523 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:53.523 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 00 00 28 33 ]
16:35:55.537 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 F5 FF 28 33 00 ]
16:35:55.583 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:55.583 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 F5 FF 28 33 ]
16:35:57.562 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 E4 FF 28 33 00 ]
16:35:57.700 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:57.700 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 E4 FF 28 33 ]
16:35:59.685 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 CF FF 28 33 00 ]
16:35:59.809 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:35:59.809 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 CF FF 28 33 ]
16:36:01.790 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 A8 FF 28 33 00 ]
16:36:01.931 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:36:01.931 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 A8 FF 28 33 ]
16:36:03.871 -> Client sends Indoor Bike Simulation Parameters to
Trainer's FTM Control Point: [ 11 00 00 84 FF 28 33 00 ]
16:36:03.948 -> -> Client Rec'd Raw FTM Control Point Response Data: [ 80
11 01 ]
16:36:03.949 -> -> Client Rec'd Raw FTM Machine Status Data: [7] [ 12 00
00 84 FF 28 33 ]
—
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANS5LSUED4ZI4LVDL3XRNZLWMXY23ANCNFSM6AAAAAASZDERFE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
At the first test I turned the pedals with my hand and I would say yes, I think it was decreasing! On the second test I did not pay attention! |
Dear Joel,
This is what I miss in the output: -> Client Rec'd Raw FTM Indoor Bike Data: [##] [#####################] |
Hello Jorghen, Here are the results after several connection attempts I tried several ways to connect and wake up the services. I have never managed to do it again 2 times in a row!!! So I don't know how to make a successful connection!!! But we have successful results!! And I really felt the different resistances. 20:01:34.256 -> Feather nRF52 Client/Central: CPS, CSC and FTMS 20:02:38.186 -> FTMS and Chars 'initialized' 20:03:28.928 -> FTMS and Chars 'initialized' 20:15:52.131 -> ----------------- Version 02.5 ------------------ Thanks, Joel |
Dear Joel, |
Hello Jorghen, I ordered the Feather but it hasn't arrived yet. I had also started modifying the FTMS_Client_V25 to wait for feedback from everyone's services. So I'm going to stop and try V.26 tonight. I received the Bluethoot BLE dongle and tested the FTMS_Server_V.22. Friendship |
I looked at your version V.26 |
Hello Jörghen, Here are the results: And the code below 18:07:13.207 -> GA and Chars 'initialized' // If FTM Feature is not found, disconnect, resume scanning, and return As you can see in the second test it does not find the speed and cadence values. Probably I should put more "While". What is weird is the value of the heart rate because the sensor is not connected (battery removed) 18:26:23.470 -> Feather nRF52 Client/Central: CPS, CSC and FTMS |
Dear Joel, |
Dear Joel, Do not take the IBD decoded data too serious, decoding the IBD data is very complicated and I would not be surprised that I understood it in a wrong way.... although it works fine with Elite Direto specifications! This is not critical for the final result, after all the MITM does not have to understand/decode everything, only what it is interested in... By the way the FTMS_Zwift_Bridge code v023 can be tested with your setup.... (Notice that you have to pedal as well! Read the instructions) Best wishes, |
Hello Jorghen, To answer the question should I always wake him up? No with the modification I made I just have to turn on the trainer. Here is the V.025 that I had to modify. I will test your V0.27. Sincerely, /********************************************************************* The code uses heavily the Adafruit supplied Bluefruit BLE libraries !! MIT license, check LICENSE for more information /*
*/ // ------------------------------------------------------------------------------------------- #include <bluefruit.h> const uint8_t MAX_PAYLOAD = 20; // Max 20 byte data size for single packet BLE transfer /* Generic Access /* Cycling Power Service
const char* client_CP_Feature_Str[] = { const char* client_Sensor_Location_Str[] = { "Other", "Top of shoe", "In shoe", "Hip", "Front wheel", "Left crank", "Right crank", "Left pedal", /*
/CSC Measurement flags/ /* CSC Feature flags */ const char* client_CSC_Feature_Str[] = {"Wheel rev supported", "Crank rev supported", "Multiple locations supported"}; BLEClientService client_CyclingSpeedCadence_Service(UUID16_SVC_CYCLING_SPEED_AND_CADENCE); /* Client Service Device Information /*
#define TIME_SPAN 2500 // Time span (delay) in millis 1000 = 1 second // TEST DATA set ----------------------------------------------------- void setup() Serial.println(" Feather nRF52 Client/Central: CPS, CSC and FTMS"); Setup_Client_FTMS(); while (Bluefruit.Scanner.isRunning()) { // do nothing else but scanning.... #endif #endif void Setup_Client_FTMS(void) // Initialize client FTM Training Status characteristic client_FTM_TrainingStatus_Chr.begin(); // Initialize client FTM Supported Power Range characteristic // Initialize client FTM Supported Resistance Level Range characteristic // Initialize client FTM Indoor Bike Data characteristic // Initialize client FTM Control Point characteristic // Initialize client FTM Status characteristic void Setup_Client_CPS(void) // Initialize CP Feature characteristics of client_CyclingPower_Service. // Initialize CP sensor location characteristics of client_CyclingPower_Service. // set up callback for receiving measurement // Initialize Control Point and set up Indicate callback for receiving responses (indicate!) void Setup_Client_CSC(void) // Initialize client characteristics of CSC. // Initialize CSC Feature characteristics of client_CSC. // set up callback for receiving measurement void Setup_Client_DIS(void) void loop() if ( Bluefruit.connected() ) void Client_Start_Scanning(void)
// Client/Central Callbacks defined Bluefruit.Scanner.setRxCallback(client_scan_callback); void Client_Enable_Notify_Indicate(void) if ( client_FTM_Status_Chr.enableNotify() ) { // MANDATORY if ( client_FTM_TrainingStatus_Chr.enableNotify() ) { // NOT MANDATORY if ( client_FTM_IndoorBikeData_Chr.enableNotify() ) { // NOT MANDATORY if ( client_CP_ControlPoint_Chr.enableIndicate() ) { // NOT MANDATORY if ( client_CSC_Measurement_Chr.enableNotify() ) { // NOT MANDATORY /**
/**
#ifdef DEBUG /**
/**
// Byte swap unsigned short // Find certain uuid in the data of the received advertising packet /**
#ifdef DEBUG /**
while (!(client_GenericAccess_Service.discover(conn_handle))) { // ---------------------------- END GA and DIS SERVICE ------------------------------------------------ // -----------------------------FTM SERVICE ------------------------------------------------------------ #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG #ifdef DEBUG // Read 32-bit client_CP_Feature_Chr value #ifdef DEBUG
#ifdef DEBUG // Test for client CSC Characteristics when the client CSC Service is existing #ifdef DEBUG #ifdef DEBUG /**
/**
uint8_t offset = 0; /**
// Handle the response message #ifdef DEBUG /**
/
*/ |
Dear Jörghen, Here is the disappointing result. The problem is still the same no feedback with V0.27 Let's not lose heart! 123456789101112131415161718192021 The code uses heavily the Adafruit supplied Bluefruit BLE libraries !! MIT license, check LICENSE for more information Message (Enter to send message to 'Adafruit ItsyBitsy nRF52840 Express' on 'COM11') |
Here is the result of FTMS_Zwift_Bridge_v023: 17:15:10.800 -> Feather nRF52840 MITM supporting: CPS, CSC and FTMS |
I'll see if with the "While" in the "Bridge V23" version if it's better The cadence is OK but I don't have the Watts!? Here is the modified code and the results : /********************************************************************* The code uses heavily the Adafruit supplied Bluefruit BLE libraries !!
*********************************************************************/ /* -----------------------------------------------------------------------------------------------------
|
I have to look info this...
How was your experience on the bike? Did you feel the resistance, grade is
about 3%... ?
Op do 15 dec. 2022 19:09 schreef le-joebar ***@***.***>:
… I'll see if with the "While" in the "Bridge V23" version if it's better
The cadence is OK but I don't have the Watts!?
[image: 20221215_181851]
<https://user-images.githubusercontent.com/61932881/207935585-0f4be135-2c92-4284-819a-80c9edc65b79.jpg>
Here is the modified code and the results :
/*********************************************************************
This is programming code for the nRF52 based Bluefruit BLE boards
The code uses heavily the Adafruit supplied Bluefruit BLE libraries !!
Adafruit invests time and resources providing open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
MIT license, check LICENSE for more information
All text must be included in any redistribution
*********************************************************************/
/*
-----------------------------------------------------------------------------------------------------
This code should work with all indoor cycling trainers that fully support,
Fitness Machine Service, Cycling Power Service and Cycling Speed & Cadence
Service
The code links a BLE Server (a peripheral to Zwift) and a BLE Client (a central to the Trainer) with a bridge
in between, the Feather nRF52 being man-in-the-middle (MITM).
The nRF52-bridge can control, filter and alter the bi-directional interchanged data!
The client-side (central) scans and connects with the trainer relevant services: FTMS, CPS and CSC. It collects
all cyling data of the services and passes these on to the server-side....
The client-side supplies the indoor trainer with target and resistance control data.
The server-side (peripheral) advertises and enables connection with cycling apps like Zwift and collects the app's
control commands, target and resistance data. It passes these on to the client-side....
The server-side supplies the app with the generated cycling data in return.
The client plus server (MITM) are transparent to the indoor trainer as well as to the training app Zwift or alike!
Requirements: Zwift app or alike, Feather nRF52 board and a FTMS/CPS/CSC supporting indoor trainer
1) Upload and Run this code on the Feather nRF52
2) Start the Serial Monitor to catch debugging info
3) Start/Power On the indoor trainer
4) Feather nRF52 and trainer (with <name>) will pair as reported in the output
5) Start Zwift on your computer or tablet and wait....
6) Search on the Zwift pairing screens for the Feather nRF52 a.k.a. "Sim <name>"
7) Pair: Power, Cadence and Controllable one after another with "Sim <name>"
8) Optionally one can pair as well devices for heartrate and/or steering (Sterzo)
9) Start the default Zwift ride or any ride you wish
1. Make Serial Monitor output window visible on top of the Zwift window
2. Hop on the bike: do the work and feel resistance change with the
road
3. Inspect the info presented by Serial Monitor.....
Your trainer's device <name> is modified by the bridge to "Sim <name>", to allow for a clear distinction
between the bridge (simulating your trainer) and your original trainer, when advertising the trainer's services!
You will notice this only when connecting to Zwift on the pairing screens! Notice: Zwift extends device names with
additional numbers for identification!
*/
//
-------------------------------------------------------------------------------------------
// COMPILER DIRECTIVE to allow/suppress SERIAL.PRINT messages that help
debugging...
// Uncomment to activate
#define DEBUG
// Restrict activating one or more of the following DEBUG directives -->
process intensive
// Have caused spurious side effects like a loss of quality of service
handling!!
//#define DEBUG_CP_MEASUREMENT
//#define DEBUG_CSC_MEASUREMENT
//#define DEBUG_FTM_INDOORBIKEDATA
//
--------------------------------------------------------------------------------------------
#include <bluefruit.h>
const uint8_t MAX_PAYLOAD = 20; // Max 20 byte data size for single packet
BLE transfer
// Struct containing Device info to administer dis/connected devices
typedef struct
{
uint8_t PeerAddress[6];
char PeerName[MAX_PAYLOAD];
uint16_t conn_handle;
bool IsConnected;
} Device_info_t;
// -----------------------------------------------------------------
// Your hardware MAC/DEVICE ADDRESSES
// Laptop/Desktop Device Address that runs Zwift, in printed format:
[00:01:02:03:04:05]
// Little Endian: in reversed order !!!!
#define LAPTOPADDRESS {0x18,0x52,0x53,0x22,0x11,0x58}
// Trainer FTMS enabled Device Address, in printed format:
[00:01:02:03:04:05]
// Little Endian: in reversed order !!!!
#define TRAINERADDRESS {0x49,0x5E,0x53,0xFC,0x9C,0xF8}
// -----------------------------------------------------------------
// Initialize connectable device registration
Device_info_t Trainer = {TRAINERADDRESS, {0x00}, BLE_CONN_HANDLE_INVALID,
false};
Device_info_t Laptop = { LAPTOPADDRESS, {0x00}, BLE_CONN_HANDLE_INVALID,
false};
Device_info_t Smartphone = { {0x00}, {0x00}, BLE_CONN_HANDLE_INVALID,
false};
//
----------------------------------------------------------------------------------
/* Generic Access
#define UUID16_SVC_GENERIC_ACCESS 0x1800
#define UUID16_CHR_DEVICE_NAME 0x2A00
#define UUID16_CHR_APPEARANCE 0x2A01
#define UUID16_CHR_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS 0x2A04 --->
not implemented
#define UUID16_CHR_CENTRAL_ADDRESS_RESOLUTION 0x2AA6 ---> not implemented
*/
BLEClientService client_GenericAccess_Service(UUID16_SVC_GENERIC_ACCESS);
// Optional
BLEClientCharacteristic client_GA_Appearance_Chr(UUID16_CHR_APPEARANCE);
// Read
uint16_t client_GA_Appearance_Value = 0;
BLEClientCharacteristic client_GA_DeviceName_Chr(UUID16_CHR_DEVICE_NAME);
// Read, Write
unsigned char client_GA_DeviceName_Data[MAX_PAYLOAD] = {};
/* Cycling Power Service
CP Service: 0x1818
CP Characteristic: 0x2A63 (Measurement)
CP Characteristic: 0x2A65 (Feature)
CP Characteristic: 0x2A5D (Location)
CP Characteristic: 0x2A66 (Control Point)
*/ BLEClientService client_CyclingPower_Service(UUID16_SVC_CYCLING_POWER);
// Mandatory BLEClientCharacteristic
client_CP_Measurement_Chr(UUID16_CHR_CYCLING_POWER_MEASUREMENT); // Notify,
Read, Mandatory BLEClientCharacteristic
client_CP_Feature_Chr(UUID16_CHR_CYCLING_POWER_FEATURE); // Read, optional
uint32_t client_CP_Feature_Flags = 0; const uint8_t CP_FEATURE_DATALEN = 4;
// Set MaxLen to 4 const char* client_CP_Feature_Str[] = {
"Pedal power balance supported", "Accumulated torque supported", "Wheel
revolution data supported", "Crank revolution data supported",
"Extreme magnitudes supported", "Extreme angles supported", "Top/bottom
dead angle supported", "Accumulated energy supported",
"Offset compensation indicator supported", "Offset compensation
supported", "Cycling power measurement characteristic content masking
supported",
"Multiple sensor locations supported", "Crank length adj. supported",
"Chain length adj. supported", "Chain weight adj. supported",
"Span length adj. supported", "Sensor measurement context",
"Instantaineous measurement direction supported", "Factory calibrated date
supported",
"Enhanced offset compensation supported"
};
BLEClientCharacteristic
client_CP_Location_Chr(UUID16_CHR_SENSOR_LOCATION); // Read, optional
uint8_t client_CP_Location_Value = 0; // UINT8
const char* client_Sensor_Location_Str[] = { "Other", "Top of shoe", "In
shoe", "Hip", "Front wheel", "Left crank", "Right crank", "Left pedal",
"Right pedal", "Front hub", "Rear dropout", "Chainstay", "Rear wheel",
"Rear hub", "Chest", "Spider", "Chain ring"
};
BLEClientCharacteristic
client_CP_ControlPoint_Chr(UUID16_CHR_CYCLING_POWER_CONTROL_POINT); //
Indicate, Write, optional
const uint16_t CP_CONTROL_POINT_DATALEN = 5;
/*
Cycling Speed and Cadence Service
CSC Service: 0x1816
CSC Measurement Characteristic: 0x2A5B
CSC Feature Characteristic: 0x2A5C
CSC Location Characteristic: 0x2A5D
CSC Control Point Characteristic:0x2A55 ---> not implemented
*/ BLEClientService
client_CyclingSpeedCadence_Service(UUID16_SVC_CYCLING_SPEED_AND_CADENCE);
// Mandatory BLEClientCharacteristic
client_CSC_Measurement_Chr(UUID16_CHR_CSC_MEASUREMENT); // Notify, Read,
Mandatory BLEClientCharacteristic
client_CSC_Feature_Chr(UUID16_CHR_CSC_FEATURE); // Read, optional const
uint8_t CSC_FEATURE_FIXED_DATALEN = 2; // UINT16 uint16_t
client_CSC_Feature_Flags = 0; const char* client_CSC_Feature_Str[] =
{"Wheel rev supported", "Crank rev supported", "Multiple locations
supported"};
BLEClientCharacteristic
client_CSC_Location_Chr(UUID16_CHR_SENSOR_LOCATION); // Read, optional
uint8_t client_CSC_Location_Value = 0;
// Shared with CPS --> client_Sensor_Location_Str[]
/* Service Device Information
#define UUID16_SVC_DEVICE_INFORMATION 0x180A
#define UUID16_CHR_MODEL_NUMBER_STRING 0x2A24
#define UUID16_CHR_SERIAL_NUMBER_STRING 0x2A25
#define UUID16_CHR_FIRMWARE_REVISION_STRING 0x2A26 ---> not implemented
#define UUID16_CHR_HARDWARE_REVISION_STRING 0x2A27 ---> not implemented
#define UUID16_CHR_SOFTWARE_REVISION_STRING 0x2A28 ---> not implemented
#define UUID16_CHR_MANUFACTURER_NAME_STRING 0x2A29
*/
BLEClientService client_DIS_Service(UUID16_SVC_DEVICE_INFORMATION); //
Optional
BLEClientCharacteristic
client_DIS_ManufacturerName_Chr(UUID16_CHR_MANUFACTURER_NAME_STRING); //
Read
char client_DIS_Manufacturer_Str[MAX_PAYLOAD] = {};
BLEClientCharacteristic
client_DIS_ModelNumber_Chr(UUID16_CHR_MODEL_NUMBER_STRING); // Read
char client_DIS_ModelNumber_Str[MAX_PAYLOAD] = {};
BLEClientCharacteristic
client_DIS_SerialNumber_Chr(UUID16_CHR_SERIAL_NUMBER_STRING); // Read
char client_DIS_SerialNumber_Str[MAX_PAYLOAD] = {};
/* Fitness Machine Service
#define UUID16_SVC_FITNESS_MACHINE 0x1826
#define UUID16_CHR_FITNESS_MACHINE_FEATURE 0x2ACC
#define UUID16_CHR_INDOOR_BIKE_DATA 0x2AD2
#define UUID16_CHR_TRAINING_STATUS 0x2AD3
#define UUID16_CHR_SUPPORTED_SPEED_RANGE 0x2AD4 ---> not implemented
#define UUID16_CHR_SUPPORTED_INCLINATION_RANGE 0x2AD5 ---> not implemented
#define UUID16_CHR_SUPPORTED_RESISTANCE_LEVEL_RANGE 0x2AD6
#define UUID16_CHR_SUPPORTED_HEART_RATE_RANGE 0x2AD7 ---> not implemented
#define UUID16_CHR_SUPPORTED_POWER_RANGE 0x2AD8
#define UUID16_CHR_FITNESS_MACHINE_CONTROL_POINT 0x2AD9
#define UUID16_CHR_FITNESS_MACHINE_STATUS 0x2ADA
*/
BLEClientService
client_FitnessMachine_Service(UUID16_SVC_FITNESS_MACHINE); // Mandatory
// Service characteristics exposed by FTM Service
BLEClientCharacteristic
client_FTM_Feature_Chr(UUID16_CHR_FITNESS_MACHINE_FEATURE); // Mandatory,
Read
const uint8_t FTM_FEATURE_FIXED_DATALEN = 8;
uint8_t client_FTM_Feature_Data[FTM_FEATURE_FIXED_DATALEN];
BLEClientCharacteristic
client_FTM_IndoorBikeData_Chr(UUID16_CHR_INDOOR_BIKE_DATA); // Optional,
Notify
BLEClientCharacteristic
client_FTM_TrainingStatus_Chr(UUID16_CHR_TRAINING_STATUS); // Optional,
Read & Notify
BLEClientCharacteristic
client_FTM_SupportedResistanceLevelRange_Chr(UUID16_CHR_SUPPORTED_RESISTANCE_LEVEL_RANGE);
// Mandatory, Read
const uint8_t FTM_SRLR_FIXED_DATALEN = 6;
uint8_t
client_FTM_SupportedResistanceLevelRange_Data[FTM_SRLR_FIXED_DATALEN];
BLEClientCharacteristic
client_FTM_SupportedPowerRange_Chr(UUID16_CHR_SUPPORTED_POWER_RANGE); //
Mandatory, Read
const uint8_t FTM_SPR_FIXED_DATALEN = 6;
uint8_t client_FTM_SupportedPowerRange_Data[FTM_SPR_FIXED_DATALEN];
BLEClientCharacteristic
client_FTM_ControlPoint_Chr(UUID16_CHR_FITNESS_MACHINE_CONTROL_POINT); //
Mandatory, Write & Indicate
BLEClientCharacteristic
client_FTM_Status_Chr(UUID16_CHR_FITNESS_MACHINE_STATUS); // Mandatory,
Notify
// ------------------------------------ START of SERVER DEFINITIONS
-----------------------------------------------------
/* Cycling Speed and Cadence Service */
BLEService server_CyclingSpeedCadence_Service =
BLEService(UUID16_SVC_CYCLING_SPEED_AND_CADENCE);
BLECharacteristic server_CSC_Measurement_Chr =
BLECharacteristic(UUID16_CHR_CSC_MEASUREMENT); // Notify, Read
BLECharacteristic server_CSC_Feature_Chr =
BLECharacteristic(UUID16_CHR_CSC_FEATURE); // Read
BLECharacteristic server_CSC_Location_Chr =
BLECharacteristic(UUID16_CHR_SENSOR_LOCATION); // Read
/* Cycling Power Service */
BLEService server_CylingPower_Service =
BLEService(UUID16_SVC_CYCLING_POWER);
BLECharacteristic server_CP_Measurement_Chr =
BLECharacteristic(UUID16_CHR_CYCLING_POWER_MEASUREMENT); // Notify, Read
BLECharacteristic server_CP_Feature_Chr =
BLECharacteristic(UUID16_CHR_CYCLING_POWER_FEATURE); // Read
BLECharacteristic server_CP_Location_Chr =
BLECharacteristic(UUID16_CHR_SENSOR_LOCATION); // Read
BLECharacteristic server_CP_ControlPoint_Chr =
BLECharacteristic(UUID16_CHR_CYCLING_POWER_CONTROL_POINT); // Indicate,
Write
/* Fitness Machine Service */
BLEService server_FitnessMachine_Service =
BLEService(UUID16_SVC_FITNESS_MACHINE);
BLECharacteristic server_FTM_Feature_Chr =
BLECharacteristic(UUID16_CHR_FITNESS_MACHINE_FEATURE); // Read
BLECharacteristic server_FTM_IndoorBikeData_Chr =
BLECharacteristic(UUID16_CHR_INDOOR_BIKE_DATA); // Notify
BLECharacteristic server_FTM_TrainingStatus_Chr =
BLECharacteristic(UUID16_CHR_TRAINING_STATUS); // Notify, Read
const uint8_t FTM_TRAINING_STATUS_FIXED_DATALEN = 2; // Fixed len
BLECharacteristic server_FTM_SupportedResistanceLevelRange_Chr =
BLECharacteristic(UUID16_CHR_SUPPORTED_RESISTANCE_LEVEL_RANGE); // Read
BLECharacteristic server_FTM_SupportedPowerRange_Chr =
BLECharacteristic(UUID16_CHR_SUPPORTED_POWER_RANGE); // Read
BLECharacteristic server_FTM_ControlPoint_Chr =
BLECharacteristic(UUID16_CHR_FITNESS_MACHINE_CONTROL_POINT); // Write &
Indicate
BLECharacteristic server_FTM_Status_Chr =
BLECharacteristic(UUID16_CHR_FITNESS_MACHINE_STATUS); // Notify
const uint8_t FTM_STATUS_DATALEN = 7; // Max Len was: [3] --> Notice that
with de Elite Direto the size is: [7] !!
/* Device Information Service helper class instance */
BLEDis server_bledis; // Read
unsigned char FirmwareRevStr[] = "0.0.0";
unsigned char HardwareRevStr[] = "0.0.0";
unsigned char SoftwareRevStr[] = "0.0.0";
/* NORDIC UART SERVICE a.k.a. NUS
NUS Service: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
NUS RXD : 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
NUS TXD : 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
*/
const uint8_t UUID_NUS_SERVICE[] = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5,
0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x01, 0x00, 0x40, 0x6E};
const uint8_t UUID_NUS_CHR_RXD[] = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5,
0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x02, 0x00, 0x40, 0x6E};
const uint8_t UUID_NUS_CHR_TXD[] = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5,
0xA9, 0xE0, 0x93, 0xF3, 0xA3, 0xB5, 0x03, 0x00, 0x40, 0x6E};
BLEService server_NordicUart_Service = BLEService(UUID_NUS_SERVICE);
BLECharacteristic server_NUS_RXD_Chr =
BLECharacteristic(UUID_NUS_CHR_RXD); // Read (Receiving Data)
BLECharacteristic server_NUS_TXD_Chr =
BLECharacteristic(UUID_NUS_CHR_TXD); // Notify (Sending Data)
// ----------------------------- The Fitness Machine Control Point data
type structure --------------------------------
// ---------------------------- Decoding is done in:
server_FTM_ControlPoint_Chr_callback ------------------------------
const uint8_t ftmcpRequestControl = 0x00;
const uint8_t ftmcpReset = 0x01;
const uint8_t ftmcpSetTargetSpeed = 0x02;
const uint8_t ftmcpSetTargetInclination = 0x03;
const uint8_t ftmcpSetTargetResistanceLevel = 0x04;
const uint8_t ftmcpSetTargetPower = 0x05;
const uint8_t ftmcpSetTargetHeartRate = 0x06;
const uint8_t ftmcpStartOrResume = 0x07;
const uint8_t ftmcpStopOrPause = 0x08;
const uint8_t ftmcpSetTargetedExpendedEngery = 0x09;
const uint8_t ftmcpSetTargetedNumberOfSteps = 0x0A;
const uint8_t ftmcpSetTargetedNumberOfStrided = 0x0B;
const uint8_t ftmcpSetTargetedDistance = 0x0C;
const uint8_t ftmcpSetTargetedTrainingTime = 0x0D;
const uint8_t ftmcpSetTargetedTimeInTwoHeartRateZones = 0x0E;
const uint8_t ftmcpSetTargetedTimeInThreeHeartRateZones = 0x0F;
const uint8_t ftmcpSetTargetedTimeInFiveHeartRateZones = 0x10;
const uint8_t ftmcpSetIndoorBikeSimulationParameters = 0x11;
const uint8_t ftmcpSetWheelCircumference = 0x12;
const uint8_t ftmcpSetSpinDownControl = 0x13;
const uint8_t ftmcpSetTargetedCadence = 0x14;
const uint8_t FTM_CONTROL_POINT_DATALEN = 19; // Control point consists of
1 opcode (byte) and maximum 18 bytes as parameters
// This ftmcp_data_t structure represents the control point data. The
first octet represents the opcode of the request
// followed by a parameter array of maximum 18 octects
typedef struct *attribute*( ( packed ) )
{
uint8_t OPCODE;
uint8_t OCTETS[(FTM_CONTROL_POINT_DATALEN - 1)];
} ftmcp_data_t;
typedef union // The union type automatically maps the bytes member array
to the ftmcp_data_t structure member values
{
ftmcp_data_t values;
uint8_t bytes[FTM_CONTROL_POINT_DATALEN];
} ftmcp_data_ut;
// Fitness Machine Control Point Data variable
ftmcp_data_ut server_FTM_Control_Point_Data;
// ----------------------- end of server_FTM_ControlPoint_Chr_callback
definitions ----------------------------
// -------------------------------------- END OF SERVER DEFINITIONS
-------------------------------------------
//
--------------------------------------------------------------------------------
// Global Server variables for decoding of INDOOR BIKE RESISTANCE
PARAMETERS
//
--------------------------------------------------------------------------------
float wind_speed = 0; // meters per second, resolution 0.001
float grade = 0; // percentage, resolution 0.01
float crr = 0; // Coefficient of rolling resistance, resolution 0.0001
float cw = 0; // Wind resistance Kg/m, resolution 0.01;
//
--------------------------------------------------------------------------------
#define TIME_SPAN 5000 // Time span in millis 1000 = 1 second
unsigned long TimeInterval = 0;
void setup()
{
#ifdef DEBUG
Serial.begin(115200);
while ( !Serial ) delay(10); // for nrf52840 with native usb
Serial.println("Feather nRF52840 MITM supporting: CPS, CSC and FTMS");
Serial.println("------------------ Version 02.3 ---------------------");
#endif
// Initialize Bluefruit with maximum connections as Peripheral = 1,
Central = 1
Bluefruit.begin(1, 1);
Setup_Client_FTMS();
Setup_Client_CPS();
Setup_Client_CSC();
Setup_Client_DIS();
Client_Start_Scanning();
while (Bluefruit.Scanner.isRunning()) { // do nothing else but scanning....
yield();
}
// wait enough time or go on when Client/Central is connected and all set!
TimeInterval = millis() + TIME_SPAN; // ADD just enough delay
while ( (millis() < TimeInterval) || (!Trainer.IsConnected) ) {
yield();
}
// Configure and Start the Device Information Service
#ifdef DEBUG
Serial.println("Configuring the Server Device Information Service");
#endif
server_setupDIS();
// Setup the Cycle Power Service, Speed & Cadence Service and FTMS
#ifdef DEBUG
Serial.println("Configuring the Server Cycle Power Service");
#endif
server_setupCPS();
#ifdef DEBUG
Serial.println("Configuring the Server Cadence and Speed Service");
#endif
server_setupCSC();
#ifdef DEBUG
Serial.println("Configuring the Server Fitness Machine Service");
#endif
server_setupFTMS();
#ifdef DEBUG
Serial.println("Configuring the Server NUS Service");
#endif
server_setupNUS();
// Setup and start advertising
#ifdef DEBUG
Serial.println("Setting up the Server-side advertising payload(s)");
#endif
server_startADV();
#ifdef DEBUG
Serial.println("Server-side is CPS, CSC and FTMS advertising!");
#endif
while (Bluefruit.Advertising.isRunning()) { // ONLY advertise!
yield();
}
TimeInterval = millis() + TIME_SPAN; // ADD just enough DELAY
// wait enough time or go on when Server/Peripheral is connected and set!
while ( (millis() < TimeInterval) || (!Laptop.IsConnected) ) {
yield();
}
#ifdef DEBUG
Serial.println("Client- and Server-side are Up and Running!");
#endif
}
// ---------------------- CLIENT SIDE FUNCTIONS -------------------------
void Setup_Client_FTMS(void)
{
// Initialize client FTM Service
client_FitnessMachine_Service.begin();
// Initialize client FTM Feature characteristic
client_FTM_Feature_Chr.begin();
// Initialize client FTM Training Status characteristic
client_FTM_TrainingStatus_Chr.setNotifyCallback(client_FTM_TrainingStatus_Notify_callback);
client_FTM_TrainingStatus_Chr.begin();
// Initialize client FTM Supported Power Range characteristic
client_FTM_SupportedPowerRange_Chr.begin();
// Initialize client FTM Supported Resistance Level Range characteristic
client_FTM_SupportedResistanceLevelRange_Chr.begin();
// Initialize client FTM Indoor Bike Data characteristic
client_FTM_IndoorBikeData_Chr.setNotifyCallback(client_FTM_IndoorBikeData_Notify_callback);
client_FTM_IndoorBikeData_Chr.begin();
// Initialize client FTM Control Point characteristic
// For receiving Control Point Responses
client_FTM_ControlPoint_Chr.setIndicateCallback(client_FTM_ControlPoint_Indicate_callback);
client_FTM_ControlPoint_Chr.begin();
// Initialize client FTM Status characteristic
client_FTM_Status_Chr.setNotifyCallback(client_FTM_Status_Notify_callback);
client_FTM_Status_Chr.begin();
#ifdef DEBUG
Serial.println("FTM Service and Chars are 'initialized'");
#endif
}
void Setup_Client_CPS(void)
{
// Initialize CPS client
client_CyclingPower_Service.begin();
// Initialize CP Feature characteristics of client_CyclingPower_Service.
client_CP_Feature_Chr.begin();
// Initialize CP sensor location characteristics of
client_CyclingPower_Service.
client_CP_Location_Chr.begin();
// set up callback for receiving measurement
client_CP_Measurement_Chr.setNotifyCallback(client_CP_Measurement_Chr_notify_callback);
client_CP_Measurement_Chr.begin();
// Initialize Control Point and set up Indicate callback for receiving
responses (indicate!)
client_CP_ControlPoint_Chr.setIndicateCallback(client_CP_ControlPoint_Chr_indicate_callback);
client_CP_ControlPoint_Chr.begin();
#ifdef DEBUG
Serial.println("CP Service and Chars are 'initialized'");
#endif
}
void Setup_Client_CSC(void)
{
// Initialize CSC client
client_CyclingSpeedCadence_Service.begin();
// Initialize client characteristics of CSC.
client_CSC_Location_Chr.begin();
// Initialize CSC Feature characteristics of client_CSC.
client_CSC_Feature_Chr.begin();
// set up callback for receiving measurement
client_CSC_Measurement_Chr.setNotifyCallback(client_CSC_Measurement_Chr_notify_callback);
client_CSC_Measurement_Chr.begin();
#ifdef DEBUG
Serial.println("CSC Service and Chars are 'initialized'");
#endif
}
void Setup_Client_DIS(void)
{
// Initialize client Generic Access Service
client_GenericAccess_Service.begin();
// Initialize some characteristics of the Generic Access Service.
client_GA_DeviceName_Chr.begin();
client_GA_Appearance_Chr.begin();
#ifdef DEBUG
Serial.println("Generic Access Service and Chars are 'initialized'");
#endif
// Initialize client Device Information Service
client_DIS_Service.begin();
// Initialize some characteristics of the Device Information Service.
client_DIS_ManufacturerName_Chr.begin();
client_DIS_ModelNumber_Chr.begin();
client_DIS_SerialNumber_Chr.begin();
#ifdef DEBUG
Serial.println("Device Information Service and Chars are 'initialized'");
#endif
}
void loop()
{
} // end loop
void Client_Start_Scanning(void)
{
/* Start Central Scanning
- Enable auto scan if disconnected
- Interval = 100 ms, window = 80 ms
- Don't use active scan
- Filter only accept CP service
- Start(timeout) with timeout = 0 will scan forever (until connected)
*/
// Client/Central Callbacks defined
Bluefruit.Central.setDisconnectCallback(client_disconnect_callback);
Bluefruit.Central.setConnectCallback(client_connect_callback);
Bluefruit.Scanner.setRxCallback(client_scan_callback);
Bluefruit.Scanner.restartOnDisconnect(false); // default is true !!! this
stage we do not want to RESTART
Bluefruit.Scanner.filterRssi(-70); // original value of -80 , we want to
scan only nearby peripherals, so get close to your device !!
Bluefruit.Scanner.setInterval(160, 80); // in unit of 0.625 ms
// Invoke callback if CPS or CSC or FTMS is advertised
Bluefruit.Scanner.filterUuid(UUID16_SVC_CYCLING_POWER,
UUID16_SVC_CYCLING_SPEED_AND_CADENCE, UUID16_SVC_FITNESS_MACHINE);
Bluefruit.Scanner.useActiveScan(true);
Bluefruit.Scanner.start(0); // 500 // 0 = Don't stop scanning or n = after
n/100 seconds
#ifdef DEBUG
Serial.println("Start Client-side Scanning for CPS, CSC and FTMS!");
delay(100); // To show print message !
#endif
}
void Client_Enable_Notify_Indicate(void)
{
// --------------------- Enable CP and CSC Notify and Indicate
------------------------------------
// Reaching here means we are ready to go, let's enable notification on
measurement chr
// ------------------------- Enable FTMS Notify and Indicate
---------------------------------------
if ( client_FTM_ControlPoint_Chr.enableIndicate() ) { // MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client FTM Control Point Response
Messages");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable indicate for Client FTM Control Point
Characteristic.");
Serial.println("FTMS (trainer) is controlled by another Client (Training
App)!");
#endif
Bluefruit.disconnect(Trainer.conn_handle);
return;
}
if ( client_FTM_Status_Chr.enableNotify() ) { // MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client FTM Status values");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable notify for Client FTM Status
Characteristic.");
Serial.println("FTMS (trainer) is controlled by another Client (Training
App)!");
#endif
Bluefruit.disconnect(Trainer.conn_handle);
return;
}
if ( client_FTM_TrainingStatus_Chr.enableNotify() ) { // NOT MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client FTM Training Status values");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable notify for Client FTM Training Status
Characteristic.");
#endif
}
if ( client_FTM_IndoorBikeData_Chr.enableNotify() ) { // NOT MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client FTM Indoor Bike Data values");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable notify for Client FTM Indoor Bike Data
Characteristic.");
#endif
}
// ------------------------- Enable FTMS Notify and Indicate
---------------------------------------
// --------------------- Enable CP and CSC Notify and Indicate
------------------------------------
if ( client_CP_Measurement_Chr.enableNotify() ) { // NOT MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client CP Measurement values");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable notify for Client CP Measurement
Characteristic.");
#endif
}
if ( client_CP_ControlPoint_Chr.enableIndicate() ) { // NOT MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client CP Control Point Responses");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable indicate for Client CP Control Point
Characteristic.");
#endif
}
if ( client_CSC_Measurement_Chr.enableNotify() ) { // NOT MANDATORY
#ifdef DEBUG
Serial.println("Ready to receive Client CSC Measurement values");
#endif
} else {
#ifdef DEBUG
Serial.println(">>> Couldn't enable notify for Client CSC Measurement
Characteristic.");
#endif
}
// ------------------------- Enable CP and CSC Notify and Indicate
--------------------------------
} // ------------------------- Enable FTMS Notify and Indicate
---------------------------------------
/**
Hooked callback that triggered when a status value is sent
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_FTM_TrainingStatus_Notify_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Client Training Status data is tranferred to the Server
// NO TREATMENT OF COMMAND !!!
server_FTM_TrainingStatus_Chr.notify(data, len); // Just pass on and
process later!
#ifdef DEBUG
uint8_t SDataLen = (uint8_t)len;
uint8_t SDataBuf[SDataLen] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw FTM Training Status Data: [%d] [ ",
len);
for (int i = 0; i < sizeof(SDataBuf); i++) {
SDataBuf[i] = *data++;
Serial.printf("%02X ", SDataBuf[i], HEX);
}
Serial.println("] ");
#endif
}
/**
Hooked callback that is triggered when an IBD value is sent
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_FTM_IndoorBikeData_Notify_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Client IBD data is tranferred to the Server
// NO TREATMENT OF COMMAND !!!
if (server_FTM_IndoorBikeData_Chr.notifyEnabled(Laptop.conn_handle)) {
server_FTM_IndoorBikeData_Chr.notify(data, len); // Just pass on and
process later!
}
#ifdef DEBUG_FTM_INDOORBIKEDATA
// Only when DEBUG_FTM_INDOORBIKEDATA is defined IDBData will be parsed
and printed!
ParseIndoorBikeData(data, len);
#endif
}
#ifdef DEBUG_FTM_INDOORBIKEDATA
void ParseIndoorBikeData(uint8_t* data, uint16_t len)
{
// ---> IBD Buffer Data Length depends on data flagged to be present !!!!
uint8_t IBDDataLen = (uint8_t)len;
uint8_t IBDDataBuf[IBDDataLen] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw FTM IBD Data: [%d] [ ", len);
for (int i = 0; i < sizeof(IBDDataBuf); i++) {
IBDDataBuf[i] = *data++;
Serial.printf("%02X ", IBDDataBuf[i], HEX);
}
Serial.print("] ");
uint8_t offset = 0;
uint16_t flags = 0;
memcpy(&flags, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
offset += 2; // UINT16
if ((flags & 1) != 0) {
// More Data
Serial.print(" More Data!");
}
// if ((flags & 2) != 0) { // (flags & 2) --> true or false Average Speed
is always(?) there (tested with Elite Direto XR)
// Average Speed 0.01
uint16_t sim_Speed = 0;
memcpy(&sim_Speed, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Speed: %d KPH", (sim_Speed / 100));
offset += 2; // UINT16
// }
if ((flags & 4) != 0) {
// Instantaneous Cadence 0.5
uint16_t inst_Cadence = 0;
memcpy(&inst_Cadence, &IBDDataBuf[offset], 2); // Transfer buffer fields
to variable
Serial.printf(" Instantaneous Cadence: %d RPM", (inst_Cadence / 2));
offset += 2; // UINT16
}
if ((flags & 8) != 0) {
// Average Cadence 0.5
uint16_t av_Cadence = 0;
memcpy(&av_Cadence, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Average Cadence: %d RPM", (av_Cadence / 2));
offset += 2; // UINT16
}
if ((flags & 16) != 0) {
// Total Distance 1
uint32_t tot_Distance = 0;
memcpy(&tot_Distance, &IBDDataBuf[offset], 3); // Transfer buffer fields
to variable
Serial.printf(" Total Distance: %d m", tot_Distance);
offset += 3; // UINT24
}
if ((flags & 32) != 0) {
// Resistance Level 1
uint16_t res_Level = 0;
memcpy(&res_Level, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Resistance Level: %d ", res_Level);
offset += 2; // UINT16
}
if ((flags & 64) != 0) {
// Instantaneous Power 1
uint16_t inst_Power = 0;
memcpy(&inst_Power, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Instantaneous Power: %d Watt", inst_Power);
offset += 2; // UINT16
}
if ((flags & 128) != 0) {
// Average Power 1
uint16_t av_Power = 0;
memcpy(&av_Power, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Average Power: %d Watt", av_Power);
offset += 2; // UINT16
}
if ((flags & 256) != 0) {
// Expended Energy -> UINT16 UINT16 UINT8
// Total Energy UINT16 1
uint16_t tot_Energy = 0;
memcpy(&tot_Energy, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Tot. Energy: %d kCal", tot_Energy);
offset += 2; // UINT16
// Energy per hour UINT16 1
uint16_t Energy_hr = 0;
memcpy(&Energy_hr, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Energy/hr: %d kCal/hr", Energy_hr);
offset += 2; // UINT16
// Energy per minute UINT8 1
uint8_t Energy_pm = 0;
memcpy(&Energy_pm, &IBDDataBuf[offset], 1); // Transfer buffer fields to
variable
Serial.printf(" Energy/m: %d kCal/m", Energy_pm);
offset += 1; // UINT8
}
if ((flags & 512) != 0) {
// Heart Rate 1
uint8_t Heart_Rate = 0;
memcpy(&Heart_Rate, &IBDDataBuf[offset], 1); // Transfer buffer fields to
variable
Serial.printf(" Heart Rate: %d HBM", Heart_Rate);
offset += 1; // UINT8
}
if ((flags & 1024) != 0) {
// Metabolic Equivalent 0.1
uint8_t Mets = 0;
memcpy(&Mets, &IBDDataBuf[offset], 1); // Transfer buffer fields to
variable
Serial.printf(" Metabolic Equivalent: %d ", Mets / 10);
offset += 1; // UINT8
}
if ((flags & 2048) != 0) {
// Elapsed Time 1
uint16_t elap_time = 0;
memcpy(&elap_time, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Elapsed time: %d s", elap_time);
offset += 2; // UINT16
}
if ((flags & 4096) != 0) {
// Remaining Time 1
uint16_t rem_time = 0;
memcpy(&rem_time, &IBDDataBuf[offset], 2); // Transfer buffer fields to
variable
Serial.printf(" Remaining time: %d s", rem_time);
offset += 2; // UINT16
}
Serial.println();
}
#endif
/**
Hooked callback that triggered when a value is sent
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_FTM_ControlPoint_Indicate_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// The receipt of Control Point settings is acknowledged by the trainer:
handle it
// Send Client's Response message to the Server
// NO TREATMENT OF COMMAND !!!
server_FTM_ControlPoint_Chr.indicate(data, len); // Just pass on and
process later!
#ifdef DEBUG
uint8_t RespBufferLen = (uint8_t)len;
uint8_t RespBuffer[RespBufferLen] = {}; // It is max 6 bytes long
// Transfer first the contents of data to buffer (array of chars)
Serial.print(" -> Client Rec'd Control Point Response: [ ");
for (int i = 0; i < sizeof(RespBuffer); i++) {
RespBuffer[i] = *data++;
Serial.printf("%02X ", RespBuffer[i], HEX);
}
Serial.println("] ");
#endif
}
/**
Hooked callback that triggered when a value is sent
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_FTM_Status_Notify_callback(BLEClientCharacteristic* chr,
uint8_t* data, uint16_t len)
{
// Client's Machine Status data is tranferred to the Server
// NO TREATMENT OF COMMAND !!!
server_FTM_Status_Chr.notify(data, len); // Just pass on and process later!
#ifdef DEBUG
uint8_t SDataLen = (uint8_t)len;
uint8_t SDataBuf[SDataLen] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw FTM Machine Status Data: [%d] [ ",
len);
for (int i = 0; i < sizeof(SDataBuf); i++) {
SDataBuf[i] = *data++;
Serial.printf("%02X ", SDataBuf[i], HEX);
}
Serial.println("] ");
#endif
}
// Byte swap unsigned short
uint16_t swap_uint16( uint16_t val )
{
return (val << 8) | (val >> 8 );
}
// Find certain uuid in the data of the received advertising packet
bool checkForUuidPresent(const uint16_t uuid, const uint8_t* reportData,
uint8_t reportDataLen)
{
// Enter uuid in printed format like 0x1826 for UUID16_SVC_FITNESS_MACHINE
// uuid is internally stored in Little Endian
for (int i = 0; i < (reportDataLen); i++) { // step 1: never miss out a
position!
if ( memcmp(&uuid, (reportData + i), 2) == 0) {
return true;
}
}
return false;
}
/**
Callback invoked when scanner pick up an advertising data
@param <https://github.com/param> report Structural advertising data
*/ void client_scan_callback(ble_gap_evt_adv_report_t* report)
{
// Since we configure the scanner with filterUuid(CPS, CSC, FTMS)
// scan_callback is invoked for devices with usually CPS service
advertised.
// However, we only do business with FTMS enabled Trainer types so check
// for UUID16_SVC_FITNESS_MACHINE to be present, if not --> keep scanning!
uint8_t Device_Addr[6] = {0};
if (!checkForUuidPresent(UUID16_SVC_FITNESS_MACHINE, report->data.p_data,
report->data.len)) {
return; // Keep scanning for FTMS trainer !!
}
memcpy(Device_Addr, report->peer_addr.addr, 6);
if ( !(memcmp(Device_Addr, Trainer.PeerAddress, 6) == 0) ) {
return; // Keep scanning for the required trainer !!
}
// Connect to device only with required services AND device address
if (Bluefruit.Scanner.isRunning()) {
Bluefruit.Scanner.stop();
}
#ifdef DEBUG
Serial.println("Found advertising Peripheral with FTMS enabled! See Raw
data packet:");
Serial.println(F("Timestamp Addr Rssi Data"));
Serial.printf("%09d ", millis());
// MAC is in little endian --> print reverse
Serial.printBufferReverse(report->peer_addr.addr, 6, ':');
Serial.print(F(" "));
Serial.print(report->rssi);
Serial.print(F(" "));
Serial.printBuffer(report->data.p_data, report->data.len, '-');
Serial.println();
#endif
Bluefruit.Central.connect(report);
}
#ifdef DEBUG
void PrintPeerAddress(uint8_t addr[6])
{
for (int i = 1; i < 6; i++) {
// Display byte by byte in HEX reverse: little Endian
Serial.printf("%02X:", addr[(6 - i)], HEX);
}
Serial.printf("%02X ", addr[0], HEX);
}
#endif
/**
Callback invoked when a connection is established
@param <https://github.com/param> conn_handle
*/ void client_connect_callback(uint16_t conn_handle) {
Trainer.conn_handle = conn_handle; // Get the reference to current
connection BLEConnection* connection = Bluefruit.Connection(conn_handle);
connection->getPeerName(Trainer.PeerName, sizeof(Trainer.PeerName));
ble_gap_addr_t PeerAddr = connection->getPeerAddr(); // Fill BLE Gap struct
memcpy(Trainer.PeerAddress, PeerAddr.addr, 6); // Copy Peer Address from
ble_gap_addr_t struct
#ifdef DEBUG
Serial.printf("Feather nRF52 (Central) connected to Trainer (Peripheral)
device: [%s] MAC Address: ", Trainer.PeerName);
PrintPeerAddress(Trainer.PeerAddress);
Serial.println();
Serial.println("Now checking all mandatory Client Services and
Characteristics!");
Serial.println("If Mandatory Services Fail --> the Client will
disconnect!");
#endif
// ---------------------------- GA and DIS SERVICE
------------------------------------------
#ifdef DEBUG
Serial.println("First checking Generic Access and Device Information
Services and Characteristics!");
#endif
// If Generic Access is not found then go on.... NOT FATAL !
while (!(client_GenericAccess_Service.discover(conn_handle))) {
if ( client_GenericAccess_Service.discover(conn_handle) ) {
#ifdef DEBUG
Serial.print(F("Found Client Generic Access\n"));
#endif
if ( client_GA_DeviceName_Chr.discover() ) {
client_GA_DeviceName_Chr.read(client_GA_DeviceName_Data,
sizeof(client_GA_DeviceName_Data));
#ifdef DEBUG
Serial.printf(" -> Client Reads Device Name: [%s]\n",
client_GA_DeviceName_Data);
#endif
}
if ( client_GA_Appearance_Chr.discover() ) {
client_GA_Appearance_Value = client_GA_Appearance_Chr.read16();
#ifdef DEBUG
Serial.printf(" -> Client Reads Appearance: [%d]\n",
client_GA_Appearance_Value);
#endif
}
} // GA
}
// If DIS is not found then go on.... NOT FATAL !
while (!( client_DIS_Service.discover(conn_handle) )){
if ( client_DIS_Service.discover(conn_handle) ) {
#ifdef DEBUG
Serial.print(F("Found Client Device Information\n"));
#endif
// 1
if ( client_DIS_ManufacturerName_Chr.discover() ) {
// read and print out Manufacturer
if ( client_DIS_ManufacturerName_Chr.read(client_DIS_Manufacturer_Str,
sizeof(client_DIS_Manufacturer_Str)) ) {
#ifdef DEBUG
Serial.printf(" -> Client Reads Manufacturer: [%s]\n",
client_DIS_Manufacturer_Str);
#endif
}
} // 1
// 2
if ( client_DIS_ModelNumber_Chr.discover() ) {
// read and print out Model Number
if ( client_DIS_ModelNumber_Chr.read(client_DIS_ModelNumber_Str,
sizeof(client_DIS_ModelNumber_Str)) ) {
#ifdef DEBUG
Serial.printf(" -> Client Reads Model Number: [%s]\n",
client_DIS_ModelNumber_Str);
#endif
}
} // 2
// 3
if ( client_DIS_SerialNumber_Chr.discover() ) {
// read and print out Serial Number
if ( client_DIS_SerialNumber_Chr.read(client_DIS_SerialNumber_Str,
sizeof(client_DIS_SerialNumber_Str)) ) {
#ifdef DEBUG
Serial.printf(" -> Client Reads Serial Number: [%s]\n",
client_DIS_SerialNumber_Str);
#endif
}
} // 3
}
} // DIS
// ---------------------------- END GA and DIS SERVICE
------------------------------------------------
// -----------------------------FTM SERVICE
------------------------------------------------------------
#ifdef DEBUG
Serial.print("Discovering Mandatory Client Fitness Machine (FTM) Service
... ");
#endif
// If FTM is not found, disconnect, resume scanning, and return
if ( client_FitnessMachine_Service.discover(conn_handle) )
{
#ifdef DEBUG
BLEConnection* conn = Bluefruit.Connection(conn_handle);
uint16_t max_payload = conn->getMtu()-3;
uint16_t data_length = conn->getDataLength();
Serial.print("Found it! ");
Serial.printf("FTMS Max Payload: %d Data Length: %d\n", max_payload,
data_length);
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client FTM Service is mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
}
#ifdef DEBUG
Serial.print("Discovering Client FTM Feature Characteristic ... ");
#endif
// If FTM Feature is not found, disconnect, resume scanning, and return
while (!( client_FTM_Feature_Chr.discover() ))
{
if ( client_FTM_Feature_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Read FTM Feature Data
client_FTM_Feature_Chr.read(client_FTM_Feature_Data, 8);
#ifdef DEBUG
Serial.print(" -> Client Reads Raw FTM Feature bytes: [8] [ ");
for (int i = 0; i < sizeof(client_FTM_Feature_Data); i++) {
Serial.printf("%02X ", client_FTM_Feature_Data[i], HEX);
} // for
Serial.println("] ");
#endif
} else {
#ifdef DEBUG
Serial.println("Disconnecting since Client FTM Feature Characteristic is
mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
Bluefruit.Connection(conn_handle);
Serial.print("Discovering Client FTM Feature not decouvert ... ");
// return;
}
}
#ifdef DEBUG
Serial.print("Discovering Client FTM Control Point Characteristic ... ");
#endif
// If FTM Control Point is not found, disconnect, resume scanning, and
return
if ( client_FTM_ControlPoint_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Send a Request Control of Machine, OpCode == 0, no values!
const uint8_t RCM[1] = {0};
client_FTM_ControlPoint_Chr.write_resp(RCM, 1);
} else {
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client FTM Control Point
Characteristic is mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
Bluefruit.Connection(conn_handle);
Serial.print("Discovering Client FTM Feature not decouvert ... ");
return;
}
#ifdef DEBUG
Serial.print("Discovering Client FTM Status Characteristic ... ");
#endif
// If FTM Status is not found, disconnect, resume scanning, and return
if ( client_FTM_Status_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client FTM Status Characteristic is
mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
}
#ifdef DEBUG
Serial.print("Discovering Client FTM Training Status Characteristic ... ");
#endif
// FTM Training Status is NOT MANDATORY
if ( client_FTM_TrainingStatus_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! Not Mandatory");
#endif
}
/*
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client FTM Characteristic is
mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
*/
#ifdef DEBUG
Serial.print("Discovering Client FTM Supported Resistance Level Range
Characteristic ... ");
#endif
// FTM SupportedResistanceLevelRange is not mandatory!
if ( client_FTM_SupportedResistanceLevelRange_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Read Supported Resistance Level Range Data
client_FTM_SupportedResistanceLevelRange_Chr.read(client_FTM_SupportedResistanceLevelRange_Data,
6);
#ifdef DEBUG
Serial.print(" -> Client Reads Raw FTM Supported Resistance Level Range
bytes: [6] [ ");
for (int i = 0; i < sizeof(client_FTM_SupportedResistanceLevelRange_Data);
i++) {
Serial.printf("%02X ", client_FTM_SupportedResistanceLevelRange_Data[i],
HEX);
} // for
Serial.println("] ");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! NOT mandatory!");
#endif
}
/*
Serial.println("Disconnecting since Client FTM Supported Resistance Level
Range Characteristic is mandatory!");
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
*/
#ifdef DEBUG
Serial.print("Discovering Client FTM Supported Power Range Characteristic
... ");
#endif
// FTM SupportedPowerRange is not mandatory!
if ( client_FTM_SupportedPowerRange_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Read Supported Resistance Level Range values
client_FTM_SupportedPowerRange_Chr.read(client_FTM_SupportedPowerRange_Data,
6);
#ifdef DEBUG
Serial.print(" -> Client Reads Raw FTM Supported Power Range bytes: [6] [
");
for (int i = 0; i < sizeof(client_FTM_SupportedPowerRange_Data); i++) {
Serial.printf("%02X ", client_FTM_SupportedPowerRange_Data[i], HEX);
} // for
Serial.println("] ");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! NOT mandatory!");
#endif
}
/*
#ifdef DEBUG
Serial.println("Disconnecting since Client FTM Supported Power Range
Characteristic is mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
*/
#ifdef DEBUG
Serial.print("Discovering Client FTM Indoor Bike Data Characteristic ...
");
#endif
// FTM Indoor Bike Data is not mandatory
if ( client_FTM_IndoorBikeData_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! Not Mandatory");
#endif
}
/*
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client FTM Indoor Bike Data
Characteristic is mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
return;
*/ // ---------------------------- End FTM SERVICE
--------------------------------------------- //
---------------------------- CP SERVICE
-------------------------------------------------- #ifdef DEBUG
Serial.print("Discovering Client Cycling Power (CP) Service ... "); #endif
// If CPS is not found, disconnect, resume scanning, and return while (!(
client_CyclingPower_Service.discover(conn_handle) )) { if (
client_CyclingPower_Service.discover(conn_handle) ) { #ifdef DEBUG
BLEConnection* conn = Bluefruit.Connection(conn_handle);
uint16_t max_payload = conn->getMtu()-3;
uint16_t data_length = conn->getDataLength();
Serial.print("Found it! ");
Serial.printf("CPS Max Payload: %d Data Length: %d\n", max_payload,
data_length);
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client Cyling Power Service is
mandatory!");
#endif
// MANDATORY so disconnect since we couldn't find the service
Bluefruit.disconnect(conn_handle);
Bluefruit.Connection(conn_handle);
Serial.print("Discovering Cycling Power (CP) Service not decouvert ... ");
return;
}
}
#ifdef DEBUG
Serial.print("Discovering Client CP Measurement characteristic ... ");
#endif
if ( !client_CP_Measurement_Chr.discover() )
{
// Measurement chr is mandatory, if it is not found (valid), then
disconnect
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client CP Measurement Characteristic
is mandatory!");
#endif
Bluefruit.disconnect(conn_handle);
return;
} else {
#ifdef DEBUG
Serial.println("Found it!");
#endif
}
#ifdef DEBUG
Serial.print("Discovering Client CP Control Point characteristic ... ");
#endif
if ( client_CP_ControlPoint_Chr.discover() )
{
// CP Control Point chr is not mandatory
#ifdef DEBUG
Serial.println("Found it!");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! NOT Mandatory!");
#endif
}
#ifdef DEBUG
Serial.print("Discovering Client CP Feature characteristic ... ");
#endif
if ( client_CP_Feature_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Configure the Cycle Power Feature characteristic
// Properties = Read
// Min Len = 1
// Max Len = 32
// B0:3 = UINT8 - Cycling Power Feature (MANDATORY)
// b0 = Pedal power balance supported; 0 = false, 1 = true
// b1 = Accumulated torque supported; 0 = false, 1 = true
// b2 = Wheel revolution data supported; 0 = false, 1 = true
// b3 = Crank revolution data supported; 0 = false, 1 = true
// b4 = Extreme magnatudes supported; 0 = false, 1 = true
// b5 = Extreme angles supported; 0 = false, 1 = true
// b6 = Top/bottom dead angle supported; 0 = false, 1 = true
// b7 = Accumulated energy supported; 0 = false, 1 = true
// b8 = Offset compensation indicator supported; 0 = false, 1 = true
// b9 = Offset compensation supported; 0 = false, 1 = true
// b10 = Cycling power measurement characteristic content masking
supported; 0 = false, 1 = true
// b11 = Multiple sensor locations supported; 0 = false, 1 = true
// b12 = Crank length adj. supported; 0 = false, 1 = true
// b13 = Chain length adj. supported; 0 = false, 1 = true
// b14 = Chain weight adj. supported; 0 = false, 1 = true
// b15 = Span length adj. supported; 0 = false, 1 = true
// b16 = Sensor measurement context; 0 = force, 1 = torque
// b17 = Instantaineous measurement direction supported; 0 = false, 1 =
true
// b18 = Factory calibrated date supported; 0 = false, 1 = true
// b19 = Enhanced offset compensation supported; 0 = false, 1 = true
// b20:21 = Distribtue system support; 0 = legacy, 1 = not supported, 2 =
supported, 3 = RFU
// b22:32 = Reserved
// Read 32-bit client_CP_Feature_Chr value
client_CP_Feature_Flags = client_CP_Feature_Chr.read32();
#ifdef DEBUG
const uint8_t CPFC_FIXED_DATALEN = 4;
uint8_t cpfcData[CPFC_FIXED_DATALEN] = {(uint8_t)(client_CP_Feature_Flags
& 0xff), (uint8_t)(client_CP_Feature_Flags >> 8),
(uint8_t)(client_CP_Feature_Flags >> 16),
(uint8_t)(client_CP_Feature_Flags >> 24)};
Serial.print(" -> Client Reads Raw CP Feature bytes: [4] [ ");
for (int i = 0; i < CPFC_FIXED_DATALEN; i++) {
if ( i <= sizeof(cpfcData)) {
Serial.printf("%02X ", cpfcData[i], HEX);
}
}
Serial.println("] ");
for (int i = 0; i < sizeof(client_CP_Feature_Str); i++) {
if ( client_CP_Feature_Flags & (1 << i) )
{
Serial.println(client_CP_Feature_Str[i]);
}
}
#endif
} else {
#ifdef DEBUG
Serial.println("NOT Found! NOT Mandatory!");
#endif
}
#ifdef DEBUG
Serial.print("Discovering Client CP Sensor Location characteristic ... ");
#endif
if ( client_CP_Location_Chr.discover() )
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// The Sensor Location characteristic
// Properties = Read
// Min Len = 1
// Max Len = 1
// B0:1 = UINT8 - Sensor Location
// Read 8-bit client CP sensor location value
client_CP_Location_Value = client_CP_Location_Chr.read8();
#ifdef DEBUG
Serial.print(" -> Client Reads CP Location Sensor: ");
Serial.printf("Loc#: %d %s\n", client_CP_Location_Value,
client_Sensor_Location_Str[client_CP_Location_Value]);
#endif
} else {
#ifdef DEBUG
Serial.println("NOT Found! NOT Mandatory!");
#endif
}
// ---------------------------- END CP SERVICE
------------------------------------------------
// ---------------------------- CSC SERVICE
--------------------------------------------------
#ifdef DEBUG
Serial.print("Discovering Cycling Speed and Cadence (CSC) Service ... ");
#endif
if ( client_CyclingSpeedCadence_Service.discover(conn_handle) ) //
UUID16_SVC_CYCLING_SPEED_AND_CADENCE
{
#ifdef DEBUG
BLEConnection* conn = Bluefruit.Connection(conn_handle);
uint16_t max_payload = conn->getMtu()-3;
uint16_t data_length = conn->getDataLength();
Serial.print("Found it! ");
Serial.printf("CSCS Max Payload: %d Data Length: %d\n", max_payload,
data_length);
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! CSC Service is Not Mandatory!");
#endif
return; // NO CSC -> end of client_connect_callback !!
}
/*
// Is Mandatory
#ifdef DEBUG
Serial.println("Not Found and disconnecting!");
Serial.println("CSC Service is mandatory!");
#endif
Bluefruit.disconnect(conn_handle);
return;
*/
// Test for client CSC Characteristics when the client CSC Service is
existing
#ifdef DEBUG
Serial.print("Discovering Client CSC Measurement CHR ... ");
#endif
if ( client_CSC_Measurement_Chr.discover() ) // UUID16_CHR_CSC_MEASUREMENT
{
#ifdef DEBUG
Serial.println("Found it! ");
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! Not Mandatory!");
#endif
}
/*
// Is Mandatory
#ifdef DEBUG
Serial.println("Not Found!");
Serial.println("Disconnecting since Client CSC Measurement CHR is
mandatory!");
#endif
Bluefruit.disconnect(conn_handle);
return;
*/
#ifdef DEBUG
Serial.print("Discovering Client CSC Location CHR ... ");
#endif
if ( client_CSC_Location_Chr.discover() ) // UUID16_CHR_SENSOR_LOCATION
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Read 16-bit client CSC sensor location value
client_CSC_Location_Value = client_CSC_Location_Chr.read8();
#ifdef DEBUG
Serial.print(" -> Client Reads CSC Location Sensor: ");
Serial.printf("Loc#: %d %s\n", client_CSC_Location_Value,
client_Sensor_Location_Str[client_CSC_Location_Value]);
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! NOT Mandatory!");
#endif
}
#ifdef DEBUG
Serial.print("Discovering Client CSC Feature CHR ... ");
#endif
if ( client_CSC_Feature_Chr.discover() ) // UUID16_CHR_CSC_FEATURE
{
#ifdef DEBUG
Serial.println("Found it!");
#endif
// Read sensor CSC Feature value in 16 bit
client_CSC_Feature_Flags = client_CSC_Feature_Chr.read16();
#ifdef DEBUG
uint8_t cscfcData[CSC_FEATURE_FIXED_DATALEN] = {
(uint8_t)(client_CSC_Feature_Flags & 0xff),
(uint8_t)(client_CSC_Feature_Flags >> 8) }; // Little Endian Representation
Serial.printf(" -> Client Reads Raw CSC Feature bytes: [2] [ ");
for (int i = 0; i < sizeof(cscfcData); i++) {
Serial.printf("%02X ", cscfcData[i], HEX);
}
Serial.println("] ");
for (int i = 0; i < sizeof(client_CSC_Feature_Str); i++) {
if ( (client_CSC_Feature_Flags & (1 << i)) != 0 )
{
Serial.println(client_CSC_Feature_Str[i]);
}
}
#endif
} else {
#ifdef DEBUG
Serial.println("Not Found! NOT Mandatory!");
#endif
}
// ---------------------------- END CSC SERVICE
---------------------------------------------
// Only now set true after ALL Mandatory Services and Char's have been
discovered !!!
Trainer.IsConnected = true;
} // End client_connect_callback
/**
Callback invoked when a connection is dropped
@param <https://github.com/param> conn_handle
@param <https://github.com/param> reason is a BLE_HCI_STATUS_CODE which
can be found in ble_hci.h
*/
void client_disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
#ifdef DEBUG
Serial.printf("Client disconnected from Peripheral Device: [%s], reason:
[%02X]\n", Trainer.PeerName, reason, HEX);
#endif
Trainer.conn_handle = BLE_CONN_HANDLE_INVALID;
Trainer.IsConnected = false;
// Force server to disconnect!
if (Laptop.conn_handle != BLE_CONN_HANDLE_INVALID) {
Bluefruit.disconnect(Laptop.conn_handle);
}
}
/**
Hooked callback that triggered when a measurement value is sent from
peripheral
@param <https://github.com/param> chr Pointer client characteristic
in this example it should be cpmc
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_CP_Measurement_Chr_notify_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Client CP Measurement data is tranferred to the Server (Zwift)
// NO TREATMENT OF RESPONSE !!!!!
if (server_CP_Measurement_Chr.notifyEnabled(Laptop.conn_handle)) {
server_CP_Measurement_Chr.notify(data, len); // Just pass on and process
later!
}
#ifdef DEBUG_CP_MEASUREMENT
uint8_t buffer[len] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw CP Data: [%d] [ ", len);
for (int i = 0; i < sizeof(buffer); i++) {
if ( i <= sizeof(buffer)) {
buffer[i] = *data++;
Serial.printf("%02X ", buffer[i], HEX);
}
}
Serial.print("] ");
uint8_t offset = 0;
// Get flags field
uint16_t flags = 0;
memcpy(&flags, &buffer[offset], 2); // Transfer buffer fields to variable
offset += 2; // UINT16
// Get Instantaneous Power values UINT16
uint16_t PowerValue = 0;
memcpy(&PowerValue, &buffer[offset], 2); // Transfer buffer fields to
variable
offset += 2; // UINT16
Serial.printf("Instantaneous Power: %4d\n", PowerValue);
// Get the other CP measurement values
if ((flags & 1) != 0) {
// Power Balance Present
Serial.print(" --> Pedal Power Balance!");
}
if ((flags & 2) != 0) {
// Accumulated Torque
Serial.println(" --> Accumulated Torque!");
}
// etcetera...
#endif
} // End cpmc_notify_callback
/**
Hooked callback that triggered when a response value is sent from
peripheral
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void
client_CP_ControlPoint_Chr_indicate_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Send Client's response message to the Server (Zwift)
// NO TREATMENT OF RESPONSE !!!!!
server_CP_ControlPoint_Chr.indicate(data, len); // Just pass on and
process later!
#ifdef DEBUG
uint8_t cpcpDataLen = (uint8_t)len;
uint8_t cpcpData[cpcpDataLen] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw CP Control Point Data: [%d] [ ", len);
for (int i = 0; i < sizeof(cpcpData); i++) {
cpcpData[i] = *data++;
Serial.printf("%02X ", cpcpData[i], HEX);
}
Serial.print("] ");
#endif
}
/**
Hooked callback that triggered when a measurement value is sent from
peripheral
@param <https://github.com/param> chr Pointer client characteristic
@param <https://github.com/param> data Pointer to received data
@param <https://github.com/param> len Length of received data
*/ void client_CSC_Measurement_Chr_notify_callback(BLEClientCharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Client CSC Measurement data is transferred to the Server (Zwift)
// NO TREATMENT OF RESPONSE !!!!!
if (server_CSC_Measurement_Chr.notifyEnabled(Laptop.conn_handle)) {
server_CSC_Measurement_Chr.notify(data, len); // Just pass on and process
later!
}
#ifdef DEBUG_CSC_MEASUREMENT
uint8_t buffer[len] = {};
// Transfer first the contents of data to buffer (array of chars)
Serial.printf(" -> Client Rec'd Raw CSC Data: [%d] [ ", len);
for (int i = 0; i < sizeof(buffer); i++) {
if ( i <= sizeof(buffer)) {
buffer[i] = *data++;
Serial.printf("%02X ", buffer[i], HEX);
}
}
Serial.print("] ");
uint8_t offset = 0;
// we define the offset that is to be used when reading the next field
// Size of variables (e.g. 2 (16) or 4 bytes (32)) are constants in
BluetoothGattCharacteristic
// these represent the values you can find in the "Value Fields" table in
the "Format" column
// Read the Flags field at buffer[0]
uint8_t flags = buffer[offset];
offset += 1; // UINT8
// we have to check the flags' nth bit to see if C1 field exists
if ((flags & 1) != 0) {
uint32_t cum_wheel_rev = 0;
memcpy(&cum_wheel_rev, &buffer[offset], 4);
offset += 4; // UINT32
uint16_t last_wheel_event = 0;
memcpy(&last_wheel_event, &buffer[offset], 2);
offset += 2; // UINT16
Serial.printf(" Cum. wheel rev.: %d Last wheel event: %d ", cum_wheel_rev,
last_wheel_event);
}
// we have to check the flags' nth bit to see if C2 field exists
if ((flags & 2) != 0) {
uint16_t cum_cranks = 0;
memcpy(&cum_cranks, &buffer[offset], 2);
offset += 2; // UINT16
uint16_t last_crank_event = 0;
memcpy(&last_crank_event, &buffer[offset], 2);
offset += 2; // UINT16
Serial.printf(" Cum cranks: %d Last crank event: %d", cum_cranks,
last_crank_event);
}
Serial.println();
#endif
}
// ---------------------- END of CLIENT SIDE FUNCTIONS
-------------------------
// ---------------------- START of SERVER SIDE FUNCTIONS
-------------------------
void server_setupNUS(void)
{
server_NordicUart_Service.begin();
// Add NUS TXD Characteristic
server_NUS_TXD_Chr.setProperties(CHR_PROPS_NOTIFY); // Type "notify"
server_NUS_TXD_Chr.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); //
readAccess, NO writeAccess
server_NUS_TXD_Chr.setMaxLen(MAX_PAYLOAD); // To be on the safe side!
server_NUS_TXD_Chr.setCccdWriteCallback(server_cccd_callback); //
Optionally capture CCCD updates
server_NUS_TXD_Chr.begin();
// Add NUS RXD Characteristic
server_NUS_RXD_Chr.setProperties(CHR_PROPS_WRITE |
CHR_PROPS_WRITE_WO_RESP); // Write with No response !!
server_NUS_RXD_Chr.setPermission(SECMODE_NO_ACCESS, SECMODE_OPEN);
server_NUS_RXD_Chr.setMaxLen(MAX_PAYLOAD); // Maxlen
server_NUS_RXD_Chr.setWriteCallback(server_NUS_RXD_Chr_callback);
server_NUS_RXD_Chr.begin();
}
void server_NUS_RXD_Chr_callback(uint16_t conn_hdl, BLECharacteristic*
chr, uint8_t* data, uint16_t len)
{
// Read data received over NUS RXD from Mobile Phone
#ifdef DEBUG
uint8_t NusRxdDataLen = (uint8_t)len; // Get the actual length of data
bytes and type cast to (uint8_t)
char NusRxdData[MAX_PAYLOAD + 1]; // Data is all ASCII !
memset(NusRxdData, 0, MAX_PAYLOAD); // set to zero
if (NusRxdDataLen > MAX_PAYLOAD) {
NusRxdDataLen = MAX_PAYLOAD; // Check for limit
}
memcpy(NusRxdData, data, NusRxdDataLen); // Transfer data to char array
// Display the raw packet data in actual length
Serial.printf(" -> Server NUS RXD Data [%d][%s]\n", NusRxdDataLen,
NusRxdData);
#endif
}
void Construct_Dev_Name(void)
{
const char prefix[] = {'S', 'i'
|
Dear Joel,
I do not see for example the trainer send IndoorBikeData to Zwift as a response to the resistance parameter settings..... nor a different output as we have seen before... I am still missing the test of the latest uploaded FTMS_Client v027 (please without you inserting extra while loops). Keep faith, somehow we will tackle the Hub connection problem! |
Dear Jörghen, Here is the disappointing result. The problem is still the same no feedback with V0.27 Let's not lose heart! 123456789101112131415161718192021 The code uses heavily the Adafruit supplied Bluefruit BLE libraries !! MIT license, check LICENSE for more information Message (Enter to send message to 'Adafruit ItsyBitsy nRF52840 Express' on 'COM11') |
Dear Joel,
That is the info your Zwift Hub is sharing...
The connInterval that it prefers is ranging between 72 and 120... so give
the value 72 a try in the Client!
Best wishes,
Jörgen.
PS
72 in units of 1.25 means: 72 * 1.25 = 90 ms
Op za 4 mrt 2023 om 14:53 schreef le-joebar ***@***.***>:
… Hi Jorghën,
Is that what you want?
[image: image]
<https://user-images.githubusercontent.com/61932881/222906193-a7291095-e081-42de-9a51-93911f55372a.png>
—
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANS5LSU7CUSHWKXE5DWMNXTW2NCORANCNFSM6AAAAAASZDERFE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Hello Jorgen, Despite many tests with different values, the best is +- 115 mms but not at all stable. A lot of : 17:40:18.954 -> Client Disconnected, reason = 0x16 17:20:34.084 -> Feather nRF52 Client/Central: CPS, CSC, HBM and FTMS 17:24:50.978 -> Feather nRF52 Client/Central: CPS, CSC, HBM and FTMS 17:37:01.110 -> Feather nRF52 Client/Central: CPS, CSC, HBM and FTMS 17:38:03.799 -> Feather nRF52 Client/Central: CPS, CSC, HBM and FTMS 17:41:48.576 -> Feather nRF52 Client/Central: CPS, CSC, HBM and FTMS |
Dear Joel, poll_delay = ConnInterval; // Synchronize SVC/CHR Discovery calls with Connection Interval Modify --> Comment out the line //poll_delay = ConnInterval; // Synchronize SVC/CHR Discovery calls with Connection Interval Try this with the connInterval value that was "most" successful around (92) 115 ms. If this new situation is improving the success rate, vary the connInterval value (from 72 and up) within the Zwift Hub Preferred Connection interval parameters // - Zwift Hub will NOT pass 100%-error-free with a value of poll_delay lower than 250 ms !!
unsigned long poll_delay = 0; // Unit is milliseconds
//--------------------------------------------------------------------------------------------------- You could give poll_delay in the above code line an arbitrary value of 50 ms.... Best wishes, |
Hello Jorgen, I think you made Jackpot with the ESP32!!!!! I had no connection failure. Here is the result : 12:04:50.863 -> ets Jun 8 2016 00:22:57 |
Jorgen, I'm trying to compile the: esp32_FTMS_Simcline_v010 at line 513 I must correct like this #include "Lifter.h" I will make the electronic connections to test everything actively. |
Dear Joel, Lifter is a class that should reside in Arduino/libraries/, if that is the case is should be addressed as #include <Lifter.h> . In other cases when it resides in the same directory as the Arduino.ino file is should be addressed as #include "Lifter.h". Have a nice weekend, |
Yes the ESP32_Client work perfectly ! |
Hello Jorgen, I can't manage to properly adjust the values for the inclination of the bike. My bike is 96cm between the 2 wheels. The axle of the rear wheel is 42 cm from the ground. For the front wheel to be at the same height, the value of the VL6180X must be at 27 cm, i.e.: Lifter.cpp But I don't think this is taken into account!!!! The maximum high and Min low stroke #define MINPOSITION 170 // VL6180X highest value top microswitch activated to mechanically stop operation Despite the values that I changed in the program after the start of the system and the self-test the wheel stood at 35cm from the ground. Can you help me to adjust the values? PS: Do you believe that with the dual core ESP32 it would be possible to add the fan speed ??? lol Friendship , |
Dear Joel, |
The private class variables are defined in Lifter Class and set to an initial value // Private variables for position control
_IsBrakeOn = true;
_IsMovingUp = false;
_IsMovingDown = false;
_TargetPosition = 400; // Choose the safe value of a flat road Class methods are defined that allow setting new values, like: void Lifter::SetTargetPosition(int16_t Tpos)
{
_TargetPosition = Tpos;
} It is safer to start with the Brakes on (true) and Moving-Up and -Down being false !!! The settings in the Simcline code have an one on one relationship with the Simcline construction and how the wheel axle position is measured, between the top of the Aluminium Extruded Profile and the gliding Axle Cylinder mount. The photos, that you have sent me, suggest (but I am not sure) that with your setup you are having the same alignment. In that case, setting values for MINPOSITION and MAXPOSITION should bring you close to a working setup... |
Hello Jorgen, I followed your instructions and tested with "esp32-nRF52_Simcline_Diagnostics_Test.ino" With the ESP 32 dual core don't you think it's possible to add the fans?. Last thing the "Simcline_v2_4_4.apk" does not work on my Samsung android A71 I have this message : Joel |
Daar Joël,
Great! Didn't you use the original test-programs before, for positioning
the actuator?
When you install the Android App the BLE library needs access rights to
your location data to function well...It asks for that right and you should
grant it!
Check in Settings, the App rights...
What is your Android version?
Best wishes,
Jörgen.
Op za 25 mrt. 2023 12:32 schreef Joel ***@***.***>:
… Hello Jorgen,
I followed your instructions and tested with
"esp32-nRF52_Simcline_Diagnostics_Test.ino"
After some mechanical adjustment everything is perfect !!! ;)
With the ESP 32 dual core don't you think it's possible to add the fans?.
Last thing the "Simcline_v2_4_4.apk" does not work on my Samsung android
A71 I have this message :
[image: image]
<https://user-images.githubusercontent.com/61932881/227714902-5cc400b1-7a06-4924-8ebe-d12dd0b9e24e.png>
Joel
—
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANS5LSXBNGIII5VAHDTA343W53JWTANCNFSM6AAAAAASZDERFE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Hello Jorgen, Then its this calm and sometimes works without problem! I thought it was because of the card. I'll send you copies of the results. On the Adafruit I did not put the Oled there the VL but it does not affect the result. There is also this I don't know if it has to do? with adafruit and Wroom 16:27:00.700 -> ./components/esp_littlefs/src/littlefs/lfs.c:1229:error: Corrupted dir pair at {0x1, 0x0} ESP32-WROOM 16:27:00.441 -> ets Jun 8 2016 00:22:57 ADAFRUIT ESP32 Feather V2 16:39:39.343 -> ets Jul 29 2019 12:21:46 |
Dear Joel, DEBUG_PRINTLN("ESP32 NimBLE SIMCLINE supporting: CPS, CSC, HBM and FTMS");
DEBUG_PRINTLN("-------------------- Version 01.1 ----------------------");
// LittleFS start the Littlefilesystem lib and see if we have persistent data ----
// This opens LittleFS with a root subdirectory /littlefs/
LittleFS.begin();
// WARNING --------- Uncomment only when strictly necessary!!! ---------
// Uncomment only the very first time the Simcline code is executed!!!
/* This creates LittleFS with a root subdirectory /littlefs/
LittleFS.format();
DEBUG_PRINTLN("Wipe out all persistent data, including any file(s)....");
*/
// Get or set (first time only) the values of relevant and crucial variables
// to persistence, with the Companion App the user can set these on the fly!
// Get or set the values of aRGVmax, aRGVmin, GradeChangeFactor in PRSdata.
if (!getPRSdata()) {
setPRSdata();
}
// LittleFS------------------------------------------------------------------------ The LittleFS error messages make it quite clear that you did NOT allow LittleFS (the first time you run the code) to create (LittleFS.format) a virtual FS-partition on the ESP32 development board that you run the Simcline code on... By the way is the Companion App working after you have allowed Bluetooth to access your location? |
Welcome to the world of ESP32:
This message covers a plethora of potential causes.....
Although you have sent 5 times the same (finally) fatal output, as I understand it: --> it will run sometimes without any problem! This points to a critical timing problem, one of the processes on Core #1 is timed out and I guess it has to do with handling of server_FTM_ControlPoint_Chr , looking at the debug output, the callback handling is active most probably when the error occurs. You have DEBUG activated with the following parts defined: #ifdef DEBUG
// Restrict activating one or more of the following DEBUG directives --> process intensive
// The overhead can lead to spurious side effects and a loss of quality of service handling!!
//#define DEBUG_HBM // If defined allows for parsing and decoding the Heart Beat Data
//#define DEBUG_CP_MEASUREMENT // If defined allows for parsing and decoding the Cycling Power Data
//#define DEBUG_CSC_MEASUREMENT // If defined allows for parsing and decoding the Cycling Speed and Cadence Data
//#define DEBUG_FTM_INDOORBIKEDATA// If defined allows for parsing the Indoor Bike Data
#ifdef DEBUG_FTM_INDOORBIKEDATA
//#define DEBUG_DECODE_IBD // If defined allows for decoding the Indoor Bike Data
#endif
//#define DEBUG_FTM_TRAININGSTATUS// If defined allows for parsing the Training Status Data
//#define DEBUG_FTM_STATUS // If defined allows for parsing the Machine Status Data
#define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined allows for parsing the Data
#define DEBUG_FTM_CONTROLPOINT_OPCODE_DATA // If defined allows for parsing and decoding Data
#endif With my experimental setup that is never causing problems but I have Arduino IDE Serial Monitor running at 115200 baud... So printing to serial monitor is fast....so less prone to timing errors! My suggestion is to try the following steps first:
//#define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined allows for parsing the Data
//#define DEBUG_FTM_CONTROLPOINT_OPCODE_DATA // If defined allows for parsing and decoding Data
|
Hello Jorgen, For the connection with Companion I will deal with it later. Here are the results with esp32_FTMS_Zwift_Bridge_v010 change as follows: //#define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined allows for parsing the Data Core Debug Level: "Error" Baud Rate 115200 I also put the Zwift images because it is each time from the same page that the reboot begins. The restart happens sometimes 1 or 2 times but as you can see in the 2nd attempt the restart lasted 4 minutes !! As soon as we arrive at this: 16:07:52.948 -> -> Client Rec'd Raw FTM Machine Status Data: [1] [1] [ 01 ] So it's stable Be careful because when there is this: 16:07:53.568 -> -> Client Rec'd Raw FTM Machine Status Data: [1] [1] [ 04 ] I'm still on the page seen above and not busy riding!! Sincerely, Joel Now the result : 15:35:18.498 -> ets Jul 29 2019 12:21:46 15:39:53.528 -> Server Connection Parameters -> Interval: [48] Latency: [0] Supervision Timeout: [960] 15:43:04.454 -> Request Control of Machine! Second test 15:57:55.669 -> ets Jul 29 2019 12:21:46 15:59:53.270 -> Server Connection Parameters -> Interval: [48] Latency: [0] Supervision Timeout: [960] 16:02:26.530 -> Request Control of Machine! ... 16:02:46.672 -> -> Client Reads HR Location Sensor: Loc#: 1 Chest .... 16:03:11.966 -> -> Client Reads HR Location Sensor: Loc#: 1 Chest ..... 16:07:32.643 -> ESP32 NimBLE MITM supporting: CPS, CSC, HBM and FTMS |
Dear Joel, void TaskWriteWithResponse(void *parameter) {
// Just pass on and process later!
if( !pRemote_FTM_ControlPoint_Chr->writeValue(ftmcpData, true) ) { // true -> WithResponse (fatal if trainer is not responding: Guru paniced!!)
pClient_FTMS->disconnect();
DEBUG_PRINTLN(">>> Error: NOT responding to FTM Control Point -> Write Value!");
}
vTaskDelete(TaskWriteWithResponseHandle);
}; After you have changed true in false like below:
in
Compile and upload the code again and run.... |
Undo!
Set Arduino IDE debug level to "debug"
And Run again!
Op vr 7 apr. 2023 15:56 schreef Joel ***@***.***>:
… Hello Jorgen,
After modifying the code
->writeValue(ftmcpData, false)) }
The system does not reboot anymore but the same thing in the code
Simcline_v011 its does not reboot anymore but the system does not go up or
down anymore!
15:42:43.082 -> Client_FTM_TrainingStatus_Chr: Not Found!
15:42:43.440 -> Client_FTM_SupportedResistanceLevelRange_Chr: Found!
15:42:43.612 -> -> Client Reads Raw FTM Supported Resistance Level Range
bytes: [6] [ 00 00 64 00 01 00 ]
15:42:43.986 -> Client_FTM_SupportedPowerRange_Chr: Found!
15:42:44.159 -> -> Client Reads Raw FTM Supported Power Range bytes: [6] [
00 00 E8 03 01 00 ]
15:42:44.497 -> Client_FTM_ControlPoint_Chr: Found!
15:42:45.167 -> Client_FTM_Status_Chr: Found!
15:42:46.175 -> Client_HeartRate_Service: Found!
15:42:46.547 -> Client_HR_Measurement_Chr: Found!
15:42:47.604 -> Client_HR_Location_Chr: Found!
15:42:47.790 -> -> Client Reads HR Location Sensor: Loc#: 1 Chest
15:43:54.121 -> erver Connection Parameters -> Interval: [48] Latency: [0]
Supervision Timeout: [960]
15:43:54.121 -> ESP32 Server connected to Client device with MAC Address:
[58:11:22:53:52:18] Conn Handle: [1]
15:43:54.121 -> Central (MyLaptop/Zwift) has to set CP/CSC/FTMS CCCD
Notify/Indicate (enable) and start....
15:43:54.653 -> Central (MyLaptop/Zwift) updated MTU to: [255] for
connection ID: 1
15:43:55.389 -> All Client (Trainer) Characteristics are Notify/Indicate
Enabled!
15:43:59.754 -> Central Updated CCCD --> Notify Enabled for Char: [0x2a63]
15:44:00.895 -> Central Updated CCCD --> Notify Enabled for Char: [0x2a5b]
15:44:01.975 -> Central Updated CCCD --> Notify Enabled for Char: [0x2ad2]
15:44:02.540 -> Central Updated CCCD --> Indicate Enabled for Char:
[0x2ad9]
15:44:03.073 -> Central Updated CCCD --> Notify Enabled for Char: [0x2ada]
15:44:03.938 -> Central Updated CCCD --> Notify Enabled for Char: [0x2a37]
15:45:35.241 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:45:35.241 -> Request Control of Machine!
15:45:40.309 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:45:40.309 -> Request Control of Machine!
15:45:45.417 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:45:45.417 -> Request Control of Machine!
15:45:50.457 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:45:50.457 -> Request Control of Machine!
15:45:55.596 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:45:55.596 -> Request Control of Machine!
15:46:00.645 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:46:00.646 -> Request Control of Machine!
15:46:05.678 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:46:05.678 -> Request Control of Machine!
15:46:10.727 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:46:10.727 -> Request Control of Machine!
15:46:15.884 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:46:15.884 -> Request Control of Machine!
15:46:20.878 -> --> Raw FTM Control Point Data [len: 1] [OpCode: 00]
[Values: 00 ]
15:46:20.878 -> Request Control of Machine!
—
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ANS5LSXHNS4A6QB4L4KGRWLXAAMKTANCNFSM6AAAAAASZDERFE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Other suggestions: |
Hello Jorgen, Should I do like this? change as follows: //#define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined allows for parsing the Data Core Debug Level: "Error" Baud Rate 115200 and in addition: Set Arduino IDE debug level to "debug" and test? |
Dear Joel, |
Here is : so esp32_FTMS_Zwift_Bridge_V010 modify as follows: //#define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined, allows to analyze the data Baud rate 115200 Main debug level: "Debug" 16:05:52.931 -> D NimBLEClient: Got Client event |
I don't know what to do anymore because it's really a funny problem !! What if I thought of delivering my trainer to you by post? Belgium to Netherlands 11 euro! What do you think about it ? |
Dear Joel,
So we have a stack problem, when TaskWriteWithResponse is called from server_FTM_ControlPoint_Chr_callback. // write with response !!! writeValue(string, bool response = false);
if(Trainer.IsConnected) {
xTaskCreate(&TaskWriteWithResponse, "Write w Response", 2048, (void *)NULL, 1, &TaskWriteWithResponseHandle);
}
memset(server_FTM_Control_Point_Data.bytes, 0, sizeof(server_FTM_Control_Point_Data.bytes)); Change the "stack depth" value from 2048 in 4096 ; we just double it and see if that is helping us out..... Lets add some extra debug messages (uncomment these defines again, to activate): #define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined, allows to analyze the data
#define DEBUG_FTM_CONTROLPOINT_OPCODE_DATA // If defined, allows to analyze and decode the data Run the program and fingers crossed! |
Hallo Jorgen, I will try your suggestion tomorrow! Vriendelijk Joel |
Hello Jorgen, Here are the results with esp32_FTMS_Zwift_Bridge_v010 change as follows: #define DEBUG_FTM_CONTROLPOINT_RESPONSE // If defined, allows to analyze the data ++++ if(Trainer.IsConnected) { +++++ ->writeValue(ftmcpData, false)) } ??????????????????????????????? Main debug level: "Debug" Baud rate 115200 : 6:29:55.383 -> D NimBLECharacteristicCallbacks: onStatus: default |
It's really and only when you're on this page!! What info does Zwift send to the trainer that it doesn't when it's on the previous page? And after he rebooted the trainer properly resumes the connection with Zwift but as he is on this page he reboots !! Once you have chosen your course then it's good time that it does not reboot! sometimes 20 minutes sometimes more sometimes less! |
Stuur even een antwoord via e-mail naar mijn email dan gaan we zo verder... |
Here is the first result with FTMS_Client_ v022
I don't know if it's good?
Next test when I receive the dongle for the Pc
Friendship,
Joel
[CFG ] SoftDevice's RAM requires: 0x20002C78
FTMS and Chars 'initialized'
CPS and Chars 'initialized'
CSCS and Chars 'initialized'
GA and Chars 'initialized'
DIS and Chars 'initialized'
Start Scanning for CPS, CSC and FTMS!
[BLE ] BLE_GAP_EVT_ADV_REPORT : Conn Handle = 65535
[BLE ] BLE_GAP_EVT_ADV_REPORT : Conn Handle = 65535
Found advertising Peripheral with FTMS service!, see the Raw Data packet:
Timestamp MAC Address Rssi Data
000000681 F8:9C:FC:53:5E:49 -60 09-02-16-18-26-18-18-18-0A-18
[BLE ] BLE_GAP_EVT_CONNECTED : Conn Handle = 0
[GAP ] MAC = F8:9C:FC:53:5E:49, Type = 1, Resolved = 0
[GAP ] Conn Interval = 20.00 ms, Latency = 0, Supervisor Timeout = 2000 ms
[BLE ] BLE_GAP_EVT_DISCONNECTED : Conn Handle = 0
[GAP ] Disconnect Reason: CONN_FAILED_TO_BE_ESTABLISHED
Feather nRF52 (Central) connected to Trainer (Peripheral) device: [] MAC Address: F8:9C:FC:53:5E:49
Now checking mandatory Client Services and Characteristics!
Discovering Client Cycling Power (CP) Service ... [DISC ] [SVC] Handle start = 1
bool BLEDiscovery::_discoverService(uint16_t, BLEClientService&, uint16_t): 79: verify failed, error = BLE_ERROR_INVALID_CONN_HANDLE
Not Found!
Disconnecting since Client Cyling Power Service is mandatory!
Client Disconnected, reason = 0x3E
BSP Library : 1.3.0
Bootloader : s140 6.1.1
Serial No : C2BA380FAD3CBBC2
--------- SoftDevice Config ---------
Max UUID128 : 10
ATTR Table Size : 4096
Service Changed : 1
Central Connect Setting
--------- BLE Settings ---------
Name : ItsyBitsy nRF52840 Express
Max Connections : Peripheral = 0, Central = 1
Address : E2:AA:F7:E9:06:E0 (Static)
TX Power : 0 dBm
Conn Intervals : min = 20.00 ms, max = 30.00 ms
Conn Timeout : 2000 ms
Central Paired Devices:
Originally posted by @le-joebar in #5 (comment)
The text was updated successfully, but these errors were encountered: