Open drmason789 opened 2 years ago
The steering values are from 0 to 255,
When mapped to Elite Sterzo, what "angle" range should these be mapped to?
I could try a linear mapping to begin with, but the steering on these bikes a is a point of undesirable calibration and I think it will be prudent to add the ability to adjust the mapping through the UI/settings, and possibly even map the incoming values to a curve, as I often feel it's more sensitive turning in one direction than the other.
When mapped to Elite Sterzo, what "angle" range should these be mapped to?
-45 to +45 if I remember correctly
I could try a linear mapping to begin with, but the steering on these bikes a is a point of undesirable calibration and I think it will be prudent to add the ability to adjust the mapping through the UI/settings, and possibly even map the incoming values to a curve, as I often feel it's more sensitive turning in one direction than the other.
Correct, we did similar thing to SS2K calibration
I've committed some serial port code reduced from serialdirconn.
Things to do:
@cagnulein Note that if you're able to run QZ on Windows (I have given up trying to get QtCreator to work with this project on Windows - issues with none of the "kits" being usable) you should be able to test this code using my TestController project in my trixter-xdream-bike repo. I've written an emulator for the bike and can even play the "game" that comes with the bike using the emulator.
- is the "bluetooth event time" available somewhere? I.e. the number of 1/1024s from an origin in time?
what do you mean? do you need access to the RTC?
- study the code to work out if I can pass the CSCS data into bike::* members, or do I have to do other things?
do you mean the cadence information? Just populate the Cadence properties inside the class, check, for example, the echelonsport.cpp class to have an example
- does it have to call bluetooth functions to send the data, or just configure bike:: members and call something?
I will add the virtual bridge in this module when you're ready, it's easier to do than to explain :)
@cagnulein Note that if you're able to run QZ on Windows (I have given up trying to get QtCreator to work with this project on Windows - issues with none of the "kits" being usable) you should be able to test this code using my TestController project in my trixter-xdream-bike repo. I've written an emulator for the bike and can even play the "game" that comes with the bike using the emulator.
there is a CI that build automatically the windows version, you have just to start a PR here
"Bluetooth Event Time" (for want of a more standard term)
In the Bluetooth spec, the CSCS (Cycling Speed and Cadence Service) data packet has CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime, where the Last*EventTime fields are unsigned 16 bit integers, where the unit of time is 1/1024s. So I would look around the code for a time source that delivers the current "Bluetooth time" which would be 1/1024th of a second since a starting point the system defines somewhere. Or perhaps I should just calculate it in this bike's class from the system clock.
CSCS information
Yes, I mean cadence information, but specifically the data from the CSCS spec (CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime).
Although I've seen there are "metric" fields:
bike::Cadence
bike::CrankRevs
bike::Distance
bike::Heart
bike::LastCrankEventTime
bike::Resistance
bike::currentSteeringAngle()
bike::Speed
I'm having to look at other code to work out what the units are.
I'm wondering what to do with the flywheel speed indicator. The bike sends back a number related to the time taken to complete the last revolution, and I can convert this to other units, and combine using samples and time between them to calculate a number of wheel revolutions, however I am yet to determine what "speed" and "distance" mean here as I've not seen a wheel radius anywhere. Angular velocity of flywheel I have, but how is distance defined?
I'm looking at echelonconnectsport.cpp.
In the Bluetooth spec, the CSCS (Cycling Speed and Cadence Service) data packet has CumulativeWheelRevolutions, LastWheelEventTime, CumulativeCrankRevolutions, LastCrankEventTime, where the Last*EventTime fields are unsigned 16 bit integers, where the unit of time is 1/1024s. So I would look around the code for a time source that delivers the current "Bluetooth time" which would be 1/1024th of a second since a starting point the system defines somewhere. Or perhaps I should just calculate it in this bike's class from the system clock.
yes add this branch https://github.com/cagnulein/qdomyos-zwift/blob/941f86ee566c833d49aca489581807e452f9a24a/src/echelonconnectsport.cpp#L246 and QZ will do the rest automatically
I'm wondering what to do with the flywheel speed indicator. The bike sends back a number related to the time taken to complete the last revolution, and I can convert this to other units, and combine using samples and time between them to calculate a number of wheel revolutions, however I am yet to determine what "speed" and "distance" mean here as I've not seen a wheel radius anywhere. Angular velocity of flywheel I have, but how is distance defined?
what typically I do when I have this kind of scenario is using the cadence value and use the cadence_sensor_ratio setting to obtain a speed value and of course a distance value
Next I will be looking at how QZ detects the device and knows which class to instantiate.
In my diagnostic app for this bike, I have it listen for a short period on all the serial ports it can open for valid data packets then auto-selects the first matching port. Hopefully I will be able to do something similar here.
@drmason789 yes autodiscovery it will be possible. Let me know if you need a hand on this
I've written some code to search the serial ports for one that delivers valid data for the bike. I've not tested this yet, as I've not got anything running in the QtCreator environment.
I am yet to determine where this would need to go for QZ to call it to detect the device. Also, if the device has many serial ports it could take a few seconds to determine if the bike is there.
Perhaps there should be a setting to specify if QZ actually looks for this type of device, and a setting to store the name of the last serial port the device was found on, so that one can be tried first.
@cagnulein Could you please recommend some next steps? e.g.
@drmason789 in my mind you should put the static function that try to understand if a serial port is good in the bluetooth.cpp, here https://github.com/cagnulein/qdomyos-zwift/blob/fe4b82740d85ef72089cdb85e36b97e96bbe4e15/src/bluetooth.cpp#L387 so if your function will return true, you can create a new trixterxdreamv1bike object and go on with your module.
Yes you can align to master with any issue I guess. Do you know how to do this, isn't it?
Once you created the bike object you should be able to see the metrics on the tiles and the virtual bridge should appear on zwift (i didn't check your code yet, but i will do as soon as you will be ready to test).
Of course, if you want you can create 2 different settings to enable your bike and/or to force to a specific serial port. Both ways are fine to me.
Are you able to compile and test now on your own on Windows?
Are you able to compile and test now on your own on Windows?
No, I've been developing on my RP4. On Windows, QtCreator takes 10+ minutes to start up, and when I open the qz project I get this:
I have explored the various options but there are long unresponsive pauses. I've got VS 2022 CE installed, and I've also installed the MSVC 2019 build tools in case those are required for one of those similarly named kits. No effect.
you need to install a kit. install the mingw 64 bit for qt 5.15.2. you can do this in the maintenance tool of q under c:\qt let me know
Il giorno sab 16 lug 2022 alle 18:30 David Mason @.***> ha scritto:
Are you able to compile and test now on your own on Windows?
No, I've been developing on my RP4. On Windows, QtCreator takes 10+ minutes to start up, and when I open the qz project I get this:
[image: image] https://user-images.githubusercontent.com/29954900/179363253-73b58ea4-f617-4dc3-8c20-208f64085978.png
I have explored the various options but there are long unresponsive pauses. I've got VS 2022 CE installed, and I've also installed the MSVC 2019 build tools in case those are required for one of those similarly named kits. No effect.
[image: image] https://user-images.githubusercontent.com/29954900/179363627-25fb4bf6-03c2-4d43-bf6d-2460e826351b.png
— Reply to this email directly, view it on GitHub https://github.com/cagnulein/qdomyos-zwift/issues/855#issuecomment-1186232997, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALYWGFP5TGIS4W5P2NAN3VULPSLANCNFSM53B4UFPQ . You are receiving this because you were mentioned.Message ID: @.***>
-- Roberto Viola Software engineer and open source enthusiast http://robertoviola.cloud
I think I updated my comment to include a screenshot of the maintenance tool while you were typing your response. I already have all the available kits for 5.15.2 installed. However, they are on my F drive. I wonder if there's a C:\ in a config somewhere that's interfering. I'll look around.
Not encouraging...
yes it seems an issue of path. try to move it to the c:\ drive (only the mingw 64bit) But i don't think it's about the drive. it could be something else, it never happened to me unfortunately:(
Installing on the C drive has made a difference, but it seems to have failed to set up the mingw 64bit kit somehow and is complaining about that.
"The Qt version is invalid: qmake does not exist or is not executable"
Same issue as before basically, but this time QtCreator let me select a kit. Still looking.
I will be leaving this for a while. I've not been able to debug yet, either on the RP4 or Windows.
The latest work is in my fork of qz, in the trixter-xdream-v1-bike branch.
The state is as follows:
trixterxdreamv1serial - this is a class based on your serialdircon class but it's not for a specific device, but it is hardcoded to 115200 bps which the Trixter bike needs. I think with a few more options (like baud rate) exposed, this could be a generic class for use by anything that delivers serial port data.
trixterxdreamv1client - This is a "business logic" class which is deliberately Qt-free - I've been able to develop it in Visual Studio with Google Test. It takes in characters (the bike delivers its data as hexidecimal text) and interprets this into a state that can be read from a function. It also provides a method to send a resistance level request, but you need to supply the instance with a delegate to write the bytes it wants to send - as I said, no Qt in this class.
trixterxdreamv1bike - This is a subclass of bike. It creates instances of the above and hooks them up. I've not been able to test this yet. It's written to detect when a new state has been detected by the trixterxdreamv1client object, and update the bike metrics. It also contains a timer to pump resistance requests to the trixterxdreamv1client object. There is a static function in this class to check a port to see if there's a bike on it, and another to find all ports and look for a bike on them. This is the code I expect you want in the bluetooth.cpp file.
If someone picks this up in the meantime, my test controller in the trixer-xdream-bike repo should be adequate for manual testing. That page has a link to a youtube video demonstrating an early version of it driving the software that comes with the bike. I should be able to find a human tester or two with real bikes to help too.
thanks! Remember that if you want to push a PR, the CI of github will build the code for you without the qt installation.
I've got the debugger working with Qt Creator 8.0.0 on Windows 10 now. MingW 8.1.0 is missing g++, gcc and gdb so I installed some extra items and rerouted a few things. Now it works, but slowly.
I've managed to have the detector code (I've put this in bluetooth.cpp) look through the serial ports and find the bike (my test controller actually), which it does by counting valid packets.
However, I'm finding it doesn't last long before I'm getting seg faults in one of the vectors in my "business logic" class. I thought maybe it was a multithreading problem, but the debugger shows only 1 thread in the code and syncing access with a mutex object simply makes the seg fault happen in the mutex object. There seems to be a few people online finding this sort of thing with QSerialPort so I've got leads.
I've changed the trixterxdreamv1serial class to use a signal to send the received data, and a slot on the trixterxdreamv1bike class. But even though the connect function for this pair returns true, the slot never gets called.
My trixterxdreamv1serial class is based on your serialdircon class, which I think looks very similar to the Qt blocking receiver example. It appears that the QObject for the receiving slot must be "moved to" the QThread that emits the signal in order for the connection to work.
@cagnulein I now have it detecting the bike (test controller actually) in bluetooth.cpp, and I have confirmed by debugging through it that the bike object is passed into the 2 template manager objects.
// Try to connect to a Trixter X-Dream V1 bike if the setting is enabled.
this->trixterxdreamv1bike = this->findTrixterXDreamV1Bike(settings);
if(this->trixterxdreamv1bike) {
this->userTemplateManager->start(trixterxdreamv1bike);
this->innerTemplateManager->start(trixterxdreamv1bike);
this->connectedAndDiscovered();
return;
}
But no information shows up in the UI, and no tiles. Also, the app reports finding my bluetooth mouse rather than the bike (I'm not sure where the application would get the bike's name from). Are you able to tell me what I need to do next? Are we ready for a PR?
@drmason789 did you pair the bike in the windows bluetooth settings?
@cagnulein The bike is not a bluetooth device.
@drmason789 hah sorry you're right, i'm following too many threads :D actually the qz discovery engine works with the bluetooth event. in the bluetooth event there is also the handler for serial and wi-fi devices so you should put there the creation of your device
i didn't actually check your code and i'm not able to review it before monday
i will check it and i will write you some notes
It seems I missed some things. Now I've had some success!
Now I need to work out why I can't increase the resistance on the tile, and find out how to display steering information.
yay! Great work!
Il giorno lun 1 ago 2022 alle 02:41 David Mason @.***> ha scritto:
It seems I missed some things Now I've had some success!
[image: image] https://user-images.githubusercontent.com/29954900/182052659-0d8d41e0-5193-493d-bf63-16a7d251d438.png
Now I need to work out why I can't increase the resistance on the tile, and find out how to display steering information.
— Reply to this email directly, view it on GitHub https://github.com/cagnulein/qdomyos-zwift/issues/855#issuecomment-1200550012, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALYWC62YGXGADBGTKJ37LVW4MMPANCNFSM53B4UFPQ . You are receiving this because you were mentioned.Message ID: @.***>
-- Roberto Viola Software engineer and open source enthusiast http://robertoviola.cloud
@cagnulein I found how to display the name of the detected device on the home screen, instead of that of my bluetooth mouse.
@drmason789
from here https://github.com/cagnulein/qdomyos-zwift/blob/ebc21c90b42f0a7dbb7defb2e5d6ad4eb4826baa/src/homeform.cpp#L1738 the resistance will write into the bike resistance object. So using the update tick in your class object and checking the requestResistance as I did here https://github.com/cagnulein/qdomyos-zwift/blob/ebc21c90b42f0a7dbb7defb2e5d6ad4eb4826baa/src/echelonconnectsport.cpp#L122 you can do the business logic to the bike about the resistance.
it was a linux/windows bug that I already fixed with this https://github.com/cagnulein/qdomyos-zwift/pull/803/files Check that these changes are in your branch
Let me know
I've got those changes:
Even if I change it to
#if defined(Q_OS_WINDOWS) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
QBluetoothDeviceInfo b;
deviceConnected(b);
#endif
it still doesn't show the tiles immediately.
Emitting deviceConnected on a fake QBluetoothDeviceInfo object in bluetooth.cpp where the device is detected did the trick:
This causes the tiles to show up, the Plus and Minus slots to be connected to the corresponding signals, and thus the resistance change requests getting to my bike object.
I'm good for now.
@cagnulein I seem to have the basics working now, cadence, speed, steering, heart rate and resistance. I've had a lot of trouble with timers not working, seg faults, and slots not being called, nothing working when an event loop added. I've got things working by avoiding the use of signals and slots in favour of simpler delegates and mutexes.
The trixterxdreamv1serial class is now a generic serial port monitor, not specifically for the Trixter X-Dream V1 bike, so it could be renamed to something less specific if you want the code to be reusable.
Do you consider it time for a PR?
If you want to use this with my emulator, you need to enable it in Settings / Bike Options.
@drmason789 yes please, create a PR. Thanks!
@cagnulein I'm currently dealing with 2 problems.
1) When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans. 2) In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.
- When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans.
of course the port is not used by your .NET application, isn't it?
2. In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.
yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?
When using the real bike, QSerialPortInfo reports the port as busy (the isBusy() function which is apparently deprecated in this version of Qt), and QSerialPort won't connect to it, citing "PermissionError". My .NET application connects to the port fine, even between QZ's scans.
of course the port is not used by your .NET application, isn't it?
No, I restarted the computer and ran QZ in QtCreator. I've for the QZ log putting the QSerialPortInfo information for all the found ports into the debug log. For the specific port the real bike is on, it says isBusy() is true, and that's false for the other ports. Yet if I run my .NET app, and connect between QZ's scans, it opens the port OK.
- In Linux, on my RP4, the code that looks for the bike, positioned in bluetooth.cpp where I believe you specified, doesn't get called. This may be because that code is in deviceDiscovered, and is actually depending on an actual blueooth device being discovered. I'm thinking the code to detect non-bluetooth devices needs to go somewhere else.
yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?
Agreed.
No, I restarted the computer and ran QZ in QtCreator. I've for the QZ log putting the QSerialPortInfo information for all the found ports into the debug log. For the specific port the real bike is on, it says isBusy() is true, and that's false for the other ports. Yet if I run my .NET app, and connect between QZ's scans, it opens the port OK.
mmm so strange, i used QSerialPort on other project on windows without any issues :( i don't know
For the Linux version on the RP4, I copied the code to detect the bike into the bluetooth class constructor, just before the test code for setting up the schwinnIC4Bike. I ran QZ, and the real Trixter X-Dream V1 bike was detected, and I was able to see inputs in the tiles! QZ still says it's "connecting..." though, so it looks like something else is required. I've not committed this yet.
Having it work on the RP is the main priority. No progress on the Windows QtSerialPort / PL2303 problem.
I can see I'm going to have to do something to provide steering calibration. I think the option to disable, and an offset and deadzone to begin with.
I'm finding QZ is very slow on both Windows and particularly the RP4. Hopefully a release build will be quicker.
on windows the slowness is about the debug log. on rpi4 be sure that you enabled the video acceleration by hardware
I've added steering calibration with new settings:
Still no ideas about the QSerialPort / PL2303 problem. The Windows PL2303HXA driver is something of a problem for these bikes. See my help page for this. I'm wondering if I use some other serial port library for C++ if it will work. QSerialPort reads my virtual COM port for the test controller without problem.
It's working on the RP4, that's the main thing.
yes we should start a parallel thread for this in the bluetooth.cpp constructor. What do you think?
My thoughts are in #889.
The problem with QSerialPort / PL2303 is solved by my PR #891 for issue #890.
@cagnulein do I need to implement something for "Peloton Resistance" ?
I can see there are pelotonToBikeResistance and bikeResistanceToPeloton functions with different implementations.
On the basis of what I see on the internet, I need to map Peloton's 0 to 100 to this bike's 0 to 250. Not sure what the real curve is (this would depend on the position and performance of the bike's solenoid) so I'll just do a linear mapping to begin with.
@cagnulein I've done this by overriding pelotonToBikeResistance using a linear scale factor, and set the m_pelotonResistance metric alongside the Resistance metric using the inverse. This seems to work with the UI.
yes a linear scale could be ok since we don't know a real curve of the resistance of this bike. Thanks
Full Details
The data comes in from the serial port at baud rate 115200 as 32-character text strings of characters 0-9 and a-f (lower case), i.e. hexadecimal text for 16 bytes. The bike this was measured on delivers packets at a rate of about 1 every 13ms.
Header is always 0x6a Steering [0, 255] Crank Position [1,60] Left Brake [135, 250] (250 when released) Right Brake [135, 250] (250 when released) Button Flags are a bitmapped field. Flywheel revolution time F: RPM = 576000.0/F approximately. Crank revolution time C: RPM = 1/(C * 0.000006) approximately. Heart rate is in beats per minute. Unknowns 02, 06, 07 are always 128, 255 and 255. 0F is the XOR of the first 15 bytes.
Button flags combined into 1 16-bit unsigned integer and subtracted from 65535 form a bitmapped field:
For Zwift, preferences would be steering, heart rate, cumulative crank and flywheel rotations calculated from time between samples and the speed and cadence themselves.
The bike supports 251 resistance levels, and these are maintained by pushing an array of 6 bytes to the bike every 10ms or so. A resistance level of 0 is maintained by sending nothing.
Each resistance packet is as follows:
Unlike the incoming data, which is hexadecimal text, this outgoing data is sent as the actual bytes specified above.