Garmin App for Pinion Smart.Shift Settings

In one of my earlier posts where I discuss my first impressions of the Pinion Smart.Shift gearbox I had fitted to my bike, I had a little moan about the missed opportunity in the Pre.Select auto shifting feature. This is a setting where a gear is automatically pre-selected if the system detects you’re coasting. In general I like it, but it becomes annoying when switched on all the time. For me I only really want it enabled on descents, where it comes into its own. What is really needed is a simple way to easily toggle it on and off as and when required. Sadly Pinion don’t agree, and the only way you can do so is via the phone app. When you’re covered in mud and have got gloves on, and your phone is safely tucked away in your backpack, it’s really not very convenient to have to get it out, take your gloves off, hunt around for the app etc., just to change a single setting.

Pinion's Phone App

After posting about my charge port 3D print, there developed a conversation in the comments beneath the post about the possibility of displaying the current gear on a bike computer. I had previously noticed that there were CAN Bus lines in the loom that I thought might potentially be a source for reverse engineering a system for getting at such data, but the initial commenter pointed out that (obviously, in hindsight) the phone app must communicate with the Smart.Shift box via Bluetooth, and was asking if I might take a look. Initially just using my phone to verify that it was indeed advertising Bluetooth, intrigued, I subsequently got myself a Bluetooth sniffer dongle and started to look a bit deeper. (Incidentally, I designed and printed a case for the dongle, having stupidly bought the caseless version.)

After some setup gymnastics I got the dongle to work with Wireshark and set to work. By going through the options one by one on my phone as I simultaneously did a network traffic capture and screen recording, I was able to correlate the changing bytes to settings I was changing on the phone. It’s quite a simple protocol, so relatively quickly I had an idea of how it worked. I would later learn that it’s essentially the CANOpen protocol, and the data was in the form of SDOs (Service Data Objects). To ease my understanding I wrote a dissector, basically a plugin that would decode the data so that Wireshark could display it in a more human readable format. Incidentally this gave me strange flashbacks to my first job out of university, where the game we worked on used Lua as its scripting language; I hadn’t used it since then.

Wireshark Bluetooth Packet Capture

I later realised that I could have saved myself quite a lot of effort here by just reverse engineering the phone app itself, but dismissed it as impractical and too time consuming versus just sniffing the data itself. However it turns out the that app is written in Javascript, and as such is trivially easy to understand. The wizened old software engineer that I am assumed that something that communicates with actual hardware would be written in a lower level compiled language like C++, or at least Java/C#/Kotlin, but no, Javascript! Learning about Bluetooth sniffing was quite interesting, regardless. Anyway, I digress.

From here I started to look into the Garmin ecosystem, to see how the Smart.Shift box might be addressed from a Garmin device, such as my aging Edge 530. They have a platform called ConnectIQ which allows for third party developers to write apps for their devices. I have various critical thoughts about this, but I’ll save those for a later addendum as they’re not really relevant to the job in hand. Anyway, to test the waters, I set about to develop a simple data field that displayed the current gear of the Pinion. It was very quick and dirty, doing the absolute bare minimum of error checking or good abstraction, the point being simply to see if it could be made to work at all.

It’s a bit slow due to accumulating polling delay, but as you can see it does work. I tried it out on an actual bike ride but found that the connection had a tendency to drop after a few minutes, unfortunately making it not very practical (although, more on this later). In order for a device to connect to the Smart.Shift box — be it the phone app or anything else — it must be put into ‘pairing’ mode. To most people who have used Bluetooth devices, ‘pairing’ implies forming a persistent, automatic connection between two devices. However, Pinion’s ‘pairing’ mode is actually a misnomer; it simply turns on Bluetooth advertising for a short period, making the gearbox visible to other devices. This means that if the connection to your Garmin drops for any reason, you must manually hold down the rear shift button for 3 seconds again to re-enable advertising and allow the device to reconnect. It’s this manual intervention that makes the gear display data field a bit impractical for continuous use. If the Garmin to Smart.Shift connection drops for any reason, and the Smart.Shift box is no longer advertising, the data field can’t reconnect itself without manual user intervention; hardly ideal.

(It was at about this time that Patrick Schlangen showed up the comments of the previous post, having independently followed much the same path as I had, and developed his own gear/battery display Smart.Shift widget. He was finding, as I was, that the connection dropped often and easily, so abandoned his effort there as a result. He subsequently took things much further and made a lot of progress reverse engineering the CAN protocol, as I had originally suggested I might attempt. Unfortunately for other reasons that seems to have also hit a dead end. Nevertheless we had a very interesting conversation. He also has a blog where he documented his project to use Shimano brake levers with his Pinion. Check it out!)

The connection persistence isn’t really a great concern from my point of view, where my use case is just turning Pre.Select on and off - having to enter ‘pairing’ mode is just an inconvenience. Speaking of which, I now turned my attention to how I might implement this function. ConnectIQ apps exist in three flavours: Data Fields, Apps and Widgets. The Data Fields are as already discussed, a non-interactive1 way of displaying data to the user. Apps are full blown applications, launched from the Garmin’s main menu. Unfortunately, they can only be started when not already engaged in an activity (i.e. a recording of your bike ride), so are not much use for anything during a bike ride. That leaves Widgets, that are very much like Apps besides a few restrictions, but crucially they are able to be executed from the Widgets menu, during an activity. So a Widget it was.

Good software engineer that I am, I decided to implement the Pinion communication part of my widget as a library (or a ‘Barrel’, in ConnectIQ terms). I’ll spare you the details, but I probably went a bit overboard here in terms of my actual needs. If you implement the code for dealing with switching one setting, other settings are obviously going to be very similar, so why not add those too? Long story short, the library can read and change all the settings that you’d realistically want to.

For the app itself I decided that since I had made this library that can read and write all the settings you’d realistically want to, I may as well just make a UI that allows you to use most of the features of the library. So that’s what I did, effectively ending up with a near-clone of the phone app. Again, I’ll spare you the details, it’s just normal software development stuff for the most part.

Scanning Connecting Syncing

I mentioned before the difficulty in establishing a connection to the Pinion from the Garmin with my quick hack gear indicator data field. For the purposes of the settings app this was less of a concern, but nevertheless it was still a present irritation, and a confusingly inconsistent one at that — sometimes it would connect and persist absolutely fine. Eventually I established that the pattern was that the connection problems only occurred when I was wearing my Polar heart rate monitor. I found it was configured to connect using ANT+, so on a whim decided to try switch it over to BLE mode, and… the Pinion/Garmin connection problems immediately went away. Whether this is the fault of Pinion, Garmin, Polar or just the general congestion of the 2.4Ghz radio band, I don’t know, but it’s nice to have that fixed. I may have another look at doing a gear indicator data field, now that this is (apparently) solved, and that I have written a nice library to talk to the Pinion.

Main Menu Information Menu

The net result of all of this is that I’m now able to turn my Pre.Select on and off during a bike ride. It still could be a lot better in that in order to do so I first have to put the Smart.Shift box in pairing mode by holding the button, then on the garmin I need to go back to the home screen, up to the status page, up again to select Widgets then select to start the widget, then select to toggle Pre.Select, then all those steps in reverse to get back to my activity. In total it’s a Konami Code-esque Back, Up, Up, Select, Select, Back, Select, which is a lot for what should really just be a long press on one of the shifter buttons or something, but in any case is still way, way, way better than having to fumble about with my phone during peak Scottish Winter.

If you have a Pinion Smart.Shift gearbox and a Garmin Edge device, in theory you could side load2 the widget and have a play with it yourself. I have GitHub configured to automatically make builds for various Edge devices, so do feel free. If there are people who actually find this useful I might publish it through more official channels, but given the number of hacks I was forced to employ and quirks I encountered during development, I’m slightly reticent to do so on devices that I don’t own and obviously can’t test myself. If you do try it out and happen to have something other than an Edge 530, do let me know your experiences and if it works for you. Obviously this is not an official Pinion product, so it may brick your gearbox or void your warranty or eat your homework, and I accept no responsibility for any of that. I mean it won’t, but if it does it’s not my fault.

Despite some frustrations, this was quite an interesting project to work on. I’m a strong advocate for trying to develop for a new software platform or paradigm on a regular basis, as you’ll almost always learn something new, and it keeps you sharp. It’s something I haven’t really done enough of in recent years.

Addendum: ConnectIQ Criticisms (skip this if you don’t care about software development)

And now for my rants

I’ll try and keep this brief. Garmin’s third party app ecosystem, ConnectIQ, is a bit of a mess:

  • Why did Garmin decide to invent their own programming language? There is a veritable panoply of existing programming languages, many of which are mature and general purpose and would have been perfectly fine for use here. They’ve needlessly given themselves an unnecessary overhead, and predictably have repeated the mistakes of other languages. MonkeyC was initially a duck-typed language, but at some point they’ve decided that yes, actually, types are quite a good idea and have retrofitted them to the language, in a manner highly reminiscent of Typescript/Javascript. The result of this is an awkward and unnatural syntax that could have been avoided. In fairness their static type checker seems to work quite well though.
  • The generated API documentation is quite poor. For example the sum total of the documentation for Menu2.updateItem(item as WatchUi.MenuItem, index as Lang.Number) as Void reads “Update a MenuItem in a Menu2.” Thanks for that. What does it update? Why do I need to update? Do I need to update? When do I need to update? They do have a slightly better set of more general discursive documentation too, but it doesn’t have a search facility and it’s quite hard to find your way around in the first place so it’s also not great.
  • There is a web forum for support, but it doesn’t appear to be used by Garmin staff, at least not recently. There are the usual 3 or 4 extremely regular users who seem to answer every question, some of which are extremely helpful, others of which are… less so, eschewing the use of source control altogether and recommending that the type system is disabled. For such a popular platform though, relying on the charity of such people for providing support to your customers isn’t really a good look. The forum software itself is very odd, seemingly needing to maintain an AJAX connection to a server in order to operate.
  • Speaking of the forum software, their bug tracking system appears to be effectively a sub-forum on this quirky software. Could you not just use GitHub or Jira like everyone else? They also seem thoroughly uninterested in bug reports, on the whole.
  • There is a module called Menu which provides a native like menu experience on the virtual machine based ConnectIQ apps. At some point they’ve realised it wasn’t very good, and added another module imaginatively called Menu2. Among its purported features is that it can supposedly be dynamically edited, a facility the original module lacked. Except it doesn’t really work. If you add or remove a menu item programmatically, nothing changes. Not even if you call the aforementioned updateItem. Unless you press a scroll button that is, then it magically appears. Also…
  • It has a simulator that runs on the desktop so that you can test your apps without having to use a real device. On the simulator, dynamically altering a Menu2 does work, making it extra annoying when you move to a real device, and you find you have to rethink your design because the simulator fails at simulating. You had one job. The simulator randomly crashes maybe 1 out of 10 times. The simulator always crashes if you have it configured to use a Bluetooth dongle and said dongle isn’t connected. (Yes, I reported the bug, no they don’t appear to care.) Using Menu2, what’s displayed on the simulator only rarely matches what you see on an actual device, again defeating the point. Honestly what on earth is going here where programs that run on a virtual machine, on the actual hardware, behave so differently on the simulator, which you would hope is just running the same virtual machine, and the same libraries? It boggles the mind.
  • The BLE implementation is… incredibly frustrating.
    • You can’t get it to return the manufacturer data from a advertising packet at all, meaning during a device scan I can’t just show the user the serial number of the gearbox(es) in their vicinity from the scan alone. Instead I have to do this ridiculous dance where if a device shows up in a scan, I temporarily connect to it, retrieve the serial number, then disconnect and resume scanning. Obviously managing the state with this approach is complicated enough, and it will inevitably be slow, but it’s super silly in that the data I need is right there, just the implementation refuses to let me at it.
    • BluetoothLowEnergy.Device.isConnected flat out doesn’t work. Once connected to a device it returns true permanently, regardless of the actual connection state.
    • BluetoothLowEnergy.BleDelegate.onConnectedStateChanged doesn’t get called if you deliberately disconnect a device, so you have to call what you need to manually.
    • When you do a scan and get back a BluetoothLowEnergy.ScanResult for a device, you only get that scan result once, regardless if the connection strength changes or the device goes out of range and comes back or whatever else. This means that unless you’re aware of this unintuitive behaviour, your device effectively vanishes into thin air. In my code I have a hack to literally turn it off and on again that avoids this happening.

OK, that wasn’t brief, I apologise. ConnectIQ really ranks pretty low in the list of platforms I’ve worked on in my reasonably extensive career, and I’ve had to use BREW. Sigh.

  1. On a touch screen device, Data Fields can be partially interactive 

  2. Connect the device to a computer via USB, copy the .prg file to /garmin/Apps, eject the device 

Share: Bluesky Facebook LinkedIn Reddit