-
-
Notifications
You must be signed in to change notification settings - Fork 78
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
usbTrainer.py refactor? #54
Comments
Restored SimulatedTrainer over morning coffee. |
Well I agree with the thinking. Quick reaction.
Since FortiusANT is my learn-python-on-the-job project I'm in for suggestions to improve. So yes - suggestions for code-improvement more than welcome! |
Hi, great! :) Refactoring to me is a multi-operation process, and I never seem to manage to have an end goal fully in mind when I start - it's iterative for me. So there are definitely still warts with what I have on my branch that I've introduced. I typically analyse the code, identify an issue area, abstract a concept, shuffle code, repeat. This started with the tuple being return, then with the fact that SendToTrainer() is a single function with multiple behaviours (stop, power, grade), then that it also handles two different protocols/calculations, etc... Conceptually, and how I think it should be reflected in code - a trainer is a trainer, regardless of the backend (USB/ANT+). Variously, they can report speed, power, cadence, HR etc, and can be instructed to simulate grade, set point power, etc... this idea isn't where I started when beginning to shuffle code last night, but it's certainly a direction. Returning the tuples was nasty, indeed. The next concept here I think is that all those values are basically a "last known state" - so storing them as attributes in the trainer class was simply the first step in refactoring. Grouping them together into a "state" object might be the next step. You should be able to query the trainer object for last known state, rather than require to wait until the next message is received... this opens up threading possibilities, decoupling GUI from USB protocol, etc. In terms of style, the biggest difference between changing from SendToTrainer(usbTrainer) to usbTrainer.SendToTrainer() was the removal of the globals (trainer_type and LegacyProtocol). These control what SendToTrainer() do, so you either pass these things in as parameters (which is noisy), have them as globals once you're in the implementation (as was the case), or encapsulate them in the same object and access them once inside (as I propose). To motivate the benefit, imagine you were to allow simultaneous control of two trainers with different values for trainer_type... and I'm not saying you would... but encapsulation is key. In my experience, and as I suggested in the first post, timing is key, and incremental adoption is also important - to avoid big-bang integration or simply forking the code and never integrating, but also to keep understanding the code. Perhaps if you were to create a branch in your repository, into which I could submit PRs, instead of master? That way, it becomes "gated" - if things are moving in the right direction, you can accept a PR and it makes subsequent changes and discussions easier. There's also a benefit to code commenting by line on the PR itself, rather than simple discussion on an issue. Sun's out - I'll go real-world cycling today. :) |
The last remarks touches an important point: getting the software work (starting from antifier) was quite a job. The proposals focus on getting the source more beautiful, a target I support, but will cost a lot of time and deliver small benefit. Would it be a commercial project that need to be future proof to support newer trainers that appear the story would be different. Suggestions welcome anyway! |
I enjoy refactoring, and my time is freely given. I would argue that in terms of maintainability of existing features, and in reducing the barriers to extending functionality or the workload therein, there is always benefit to be had in code clean up. I am not criticising any work to date - it's just fun (for me) to try to understand how best to shape and mould code to more elegantly express the problem and its solution. Apart from that, a pet project through which to learn is perhaps a perfect candidate for beautification, where there is no commercial risk and opensource is a great platform for collaboration. I guess the intentions of embarking on such an effort would be to refine the domain-specific abstractions, minimise coupling, maximise cohesion, and generally exercise the programming muscles without necessarily introducing additional support load (features), simply to make maintenance easier, or if not, just for personal gain. A bit philosophical, but I'd love to help. Even watching for old trainers on eBay. |
Thanks for the offer! |
I'm following your progress, well done! Not easy thing to do remotely. |
Great assistance from @darkpotpot! PS. did we introduce ourselves already, my name is Wouter and I'm from the Netherlands. |
@WouterJD Did you mean #51 ? If so, please.... would you entertain accepting some changes from me, in the direction of refactoring usbTrainer.py first? I am very keen on this, and I think it is the right time. I deleted my old refactor branch, as I went too far too soon, I fear. Same thoughts remain in my head though. I am starting again, and am convinced that the direction in which I think it should go will help. I think it needs careful planning though. My thoughts are:
I think if it is to work at all, then it should be in stages (ie incremental) and it will take a short while to iterate through development/review/merge for these these stages... for example:
I really, honestly, believe this will make a difference - certainly steps 1 and 2, but you often "can't see the wood for the trees" and the sense behind further refactoring only becomes apparent when you get other things out of the way. It could be done in as little as a day or two, depending on your time available to review. I think crucially, if it's to your new branch, not to master, then master is safe, and worst case the exercise is futile and you discard the branch. I don't think that will happen. In a sense, I don't want to introduce the more complicated "virtual speed" notion part of my power curve work without first refactoring otherwise it becomes a bit spaghetti-like and difficult to follow. What do you think? I'm ambitious, and hopeful... It could be as simple as you create a branch, and watch me work as I create 3, 4 or 5 PRs into your branch. |
Approach ok. Perhaps have a WebEx, discussing how to work with branches etc. I would have time Wednesday evening if you would think that's an idea. 4 I doubt since Vortex is not a usbtrainer but an ant-device, but suggestions welcome. |
Advantage of this is that when the Fortius works, the code is untouched when working on imagic |
Note that l, there are two parameters: Today mapped on "legacy" but it seems all mixes are on the market. |
Let's do refactoring first, power-curve as second. When done other ideas.... |
@WouterJD Excellent. Yes, a live chat would be a good idea. Regression testing the biggest issue, but you have a community of users, I imagine a few might be willing to pull down a branch, run it and report back. |
@WouterJD curious - what development tools do you use? I've been using Virtual Studio Code on this project and so far, quite happy. Good integration with git/github, python, etc. |
You won't believe it: Notepad++, google/github. |
I was using vim until I downloaded VSCode. https://code.visualstudio.com/ Left panel shows files, which ones have been changed, right panel shows an overview of the file (very zoomed out, if you like), very-very-bottom-left shows which branch you have checked out, main panel shows you by the small coloured bits beside the line numbers where changes have been made - click any one of them and you get a view of exactly what you changed (red old, green new). It has as linter (a tool that analyzes source code to flag programming errors, bugs), and just generally fits a lot onto the screen. I've not used github desktop. But VSCode seems to be reasonable at what I need it for. I do a little command line too, but just because I'm used to that for some commands. It's the first Python project I've really worked on. Other IDEs are available, but VSCode is fine for me for now. I'd give it a go if you have half an hour to have a play. It automatically prompts you to install the python linter etc. |
I don't know whether you got the copy of my request to @totalreverse, hence this not. I suggest to rename this issue into "Improve support of i-Magic, i-Flow, -Vortex trainer types" because that's what people look for and that's in the end the purpose of refactoring. Agree? |
I'd give it a go if you have half an hour to have a play |
About renaming the issue - in my head, it's about encapsulating trainer characteristics and idiosyncrasies, abstracting operations, and separating concerns. It's not about improving support for trainer types, more about improving (hopefully) the maintainability of the code itself. The purpose is to end up with cleaner code, and not really and end-user concern. So a better title than this issue has now at least might be "Improve encapsulation and abstraction in usbTrainer.py and surrounding code". We'll try to keep any functional/behavioural changes out of the work associated with this - it may introduce regressions, but willing test subjects help mitigate this. |
Just to run ahead on our meeting lateron next week I would expect that in future will will say If we have different classes e.g. clsFortius and clsMagic, should the initial call be: Or is the class going to be:
where What do you think? |
BUT We have two interfaces (48 and 64) and two engines (formula 1 and 2) so we need an idea how to support any mix of the two (in this case 4). I call it a night for today - cu later! |
I would typically want to have the GetTrainer() function contain all the logic for determining which trainer is which - certainly detecting between the USB device IDs, but even the check for "if clv.Tacx_iVortex:" could go inside GetTrainer(). What trainer object you get is a function of a) command line parameters (-s for example), and b) what is actually connected. So let that function do that work - only one GetTrainer() function. The trainer object returned should then be able to be used by calling code (almost?) identically regardless of which device it is. They all have concepts of Speed and Power, Cadence, HR only in some cases. Rather than SendToTrainer() which I don't like too much, I will suggest a move towards "SetPower()", "SetGrade()", "Stop()" operations etc that work regardless of what type of trainer object was returned by GetTrainer(). Further, SetGrade() for example does involve some calculations... however some are totally trainer-independent calculations (eg Grade2Power()), others are trainer dependent as they rely on conversion factors (Power2Resistance(), Wheel2Speed()). These kind of things become more apparent when you start to shuffle code around. Mainly, my point is not to "couple" fine details of specific trainers to the main loop, and to conceptually separate interface from implementation. |
Agree and curious what implementation you will suggest |
@WouterJD Me too... I'm working on it again in stages. Stage 1 is in my fork already, stage 2 I'm working on (got sidetracked with HRM/SCS and Button events) and should push later today - I think this has been the trickiest and hope it's not itself too big a step/jump. Stage 3 will be SendToTrainer work, etc... |
Perhaps if you were to create a branch in your repository, into which I could submit PRs, instead of master? Done: branch "#54 usbTrainer.py refactor" created I guess this is the idea of it, so we can work in the same repository instead of you working in your own fork. Right? |
Excellent. In my mind the idea is:
The reason I think this overall approach is a good way to do things is to a) avoid one big massive PR that contains several conceptual changes at once, and b) to be able to focus on each conceptual change and discuss it before you accept it into your own fork. Each PR will build on the previous. I have about 4 or 5 stages in mind I guess, perhaps the last couple are less clear as yet but should be as I get there. I hope to have all work I believe strongly in ready for Wednesday, so that the object of the exercise is to discuss my changes in stages, exercise some PRs and merges as we go, and then relax away from the detailed code review and either on the same call or another, think about a) where we've got to, b) where the risk lies, c) anything we've noted down along the way. Hopefully the summary will be along the lines of:
I suggest that if we reach this point, and the exercise is complete, that you consider hard whether and when to proceed and merge to master - perhaps 2.7. The longer a significant branch like a refactor branch stays alive, the harder it is to keep it up to date. In principle you could have any number of small feature branches on the go at once... perhaps CYCPLUS, LCD, Vortex, I don't know what. Though each may be small, once merged to master, then the refactor branch needs to be updated wrt any changes now present on master. A significantly changed branch such as refactor is very possibly going to imply merge conflicts for even the smallest change. And keeping it up to date and ready to merge to master is crucial. Which is why you're absolutely right to raise regressions and your and my lack of ability to test everything, and why we will need to rely on the community of users to test a bleeding edge branch and then at some point soon, just go with it. On a technical note rather than logistical, an interesting study will then will be to look at how implementing a non-linear-virtual-speed-relationship will look upon the new code, compared with how it might have looked pre-refactor. |
(wow, that was long) |
I will read that in detail later. Refer to:
My findings:
My conclusion today: *) Vortex is now treated like HeadUnitNew due to lack of Vortex user providing test-results of a power-curve test. "We" are badly distracted because of this CYCPLUS dongle stuff. Next main thing is to improve the Grade2Resistance formula; #51 |
Agree with the long text; I hope we have the refactoring thing done asap so we can close and proceed with #51 which is as least as challenging because it touches the real purpose of the project: make old Tacxes work with Zwift. |
All sounding positive. Agree with your comments re: the conceptual/naming split re: trainer types. |
:-) |
Further possible requirement to keep in mind while performing and evaluating refactor: BT interface rather than ANT. FortiusBT as a separate program vs introduction of BT functionality in FortiusANT. I'd be in favour of the latter - supports the logic behind decoupling ANT from Trainer and from GUI threading, etc - thus making it much easier to introduce BT<-->Trainer as functional alternative to ANT<-->Trainer. Quite excited by this idea myself |
Well let's not get off track. The purpose of FortiusANT is to provide a bridge between USB and ANT. That's it. And that should work as good as possible. There are two real issues:
Risk is that we're getting too distracted from the main objective. |
@WouterJD Agreed. Just making note - always good to consider "what if" and "does this refactor direction support/preclude X..." Not suggest it goes in now or that we detour, just good to validate architecture against things like this. |
A broader scope would be "FortiusANT is the best bet for people to integrate old TACX trainers with modern software" - and that could well be BT in some cases - eg a mobile device that has BT but not ANT. Anyway, it's not a bad idea imo, in time. Edit: especially if someone else is able to put time in to develop it - least we can do is consider the potential for it during refactor and identify where the scalpel needs to cut, if you like. |
Well.... waiting for somebody going to implement bluetooth |
@WouterJD you're right, lets not get off track. I was just voicing my interest to look into it myself in the future, not stating a development requirement. |
Implemented in 3.0 (yes, iVortex also) |
Hi,
I have, for the last hour or two, been having a play in and around usbTrainer.py and FortiusAnt.py.
My thoughts to begin with were to encapsulate a little
Having broken simulation, and reached the point in the evening where I will stop, I though I'd post this issue for discussion.
Having two very distinct sets of behaviour (around LegacyProtocol for the iMagic), and now introducing the iVortex into the mix, it seems to me like a sensible direction to refactor further and separate concerns... each trainer type (or family) having it's own trainer class perhaps, with it's own send()/receive() implementations, yet sharing a common interface, and beginning to abstract a little to setLoad() or setGrade() functions, and a periodic doUpdate() or similar, as opposed to the manual Send/Receive pattern in FortiusAnt currently.
I've been bitten before refactoring someone else's code and there are several things to consider. I can be over keen. Even if there is appetite, timing can be important. That said, it feels like with work towards iVortex it could be both challenging and rewarding to refactor at this point. It would only really work with clear, helpful, incremental changes. Otherwise too much just becomes a fork that may never integrate, and that is unhelpful.
I've pushed to a "Refactor" branch in my fork. Feel free to have a look.
If you do have a look and get your head around my changes, worth perhaps me adding here some quick notes on my thoughts on next steps for context:
The first step would be for me to restore simulation, discuss/fix my break to SpeedT, CadenceT etc, and consider raising a PR of something working, moving in an agreed direction.
Interested to hear your thoughts on this. Whether it's welcomed, if so thoughts on timing, direction, scope, scale, etc... if not, I'll be happy enough to just Ride On. :)
I am of course very grateful for the software as is - I stumbled across the trainer for not-too-much-money last year, and am delighted that it appears as capable as it is, even now. Thanks to you and those before you. A great lockdown activity.
The text was updated successfully, but these errors were encountered: