Tuesday, October 1, 2024

Monkey C

I bought my first smartwatch back in 2011, but I wasn't at all happy with it. As I recall, there were three reasons:
  • The watch face was black most of the time, and you had to "do something" to get it to light up;
  • The battery barely lasted for a day;
  • It wasn't as smart as they made out, and you couldn't program it.
I said I wouldn't buy another one until they sorted all this out. In 2017, I thought they had and bought a Garmin vivosport, which had a continuously-on display, a battery that lasted a week, and could allegedly be programmed in a language called Monkey C. I remember sitting in a hotel room trying to "sideload" a sample application only to find that the vivosport did not actually support installing custom apps, just the ones that it came with. Oh, well, two out of three ain't bad.

When that watch died two years later, I bought a vivoactive, making sure when I did that it supported custom apps. By that time, I had started writing this blog and thought that further investigation of the watch and its ecosystem would be something good to do here. And then didn't.

But over the years, I've thought of a number of applications that I would be interested in having run on the watch, one of which keeps on coming up with regularity: since I live in the centre of Manchester, I would like to be able to know which trams are going where on a frequent basis, especially the thorny question of whether I should walk to Market St, Piccadilly Gardens or St Peter's Square to catch a tram to Altrincham.

A couple of years ago, while ruminating on this problem, I found that Metrolink had a real-time data feed that can return all the information on all the departure boards in the city. These boards are apparently called PIDs (Platform Information Displays) and there is an OData feed to access them. I was going to include a link to how to access this yourself, but apparently the Bee Network has decided to go in a different direction, and has deprecated this feed. At the time I wrote a (simple?) and hacky PHP script to interrogate the data and show the times of the next trams leaving certain stations in the city centre. I won't show this code here, although I have checked it in to the repository as samples/metrolink.php if you want to use it as evidence against me. No, there are no prizes for figuring out what it does or how, but perhaps there should be.

Fortunately for me, in spite of the feed being deprecated, I can still gather the data, but sadly you cannot follow along with the code that directly accesses the Metrolink data. On the other hand, I have decided to access that from a web server and have that return a simplified form of the data to the watch - so you can follow along with the watch code accessing my server.

The Plan

So, here's the plan. Garmin watches have things they call "widgets". A widget is something that you can interact with to a limited extent and that you can access by swiping up or down the "widget carousel". Once there, you can swipe (I believe) left or right to see different "views" of the same widget, or tap on a selectable area to choose a specific area of information.

My thought is to have a predetermined number of "well-known" routes - city centre to Altrincham, leaving the Trafford Centre, Oldham to Manchester, for example - that I can swipe between. The "default view" will always be the one I looked at last, so that when I am thinking about catching a tram, I can "prime" it with the route I want and then it will appear more quickly when I swipe at my watch.

For now, I plan to have these hardcoded into the watch software, but have that send a request to the server stating the starting stations and lines that I am interested in. The server will then return a list of pairs (station and line) with a list of times of next trams. The server code (in PHP) will be responsible for contacting the Metrolink OData server and processing all the data in the system to produce this minimal amount of data.

This amount of code would ensure that I know how to:
  • Get up and running with Monkey C, including setting up a development environment and sideloading to the watch;
  • Build a basic "widget" app;
  • Access the internet from Monkey C and process a JSON resource.
An extension activity would be to move the hardcoded routes into an app on the phone: the app enables the user to customise the routes they think they would like to take and to have each of these be one of the views in the widget. This would obviously require writing a whole Android app, but it would allow investigation of the communication between phone and watch, and more investigation of storing information on the watch.

The Data

I know I struggle a lot with other people's data decisions. My own intuitions about how to represent a real world system in a formal system are so strong that I often struggle to relate to the approaches that other people take, particularly when I cannot sit down with them and try to understand their viewpoint. This is a case in point.

In my mind, the key concepts I would model on a transit network would be the idea of a "tram", "location" and "station". And I would at any time identify where a "tram" was by giving a "location" which consisted of the amount it was (probably in time units) past the last "station" it stopped at. I would then have a network topography that indicated the connections (and expected time) between each pair of stations.

The Metrolink data is not like that. Presumably they have some "raw" data like this somewhere, from which they calculate all the data they do give you. But the data that is streamed is literally the data that is presented on the departure boards on all the stations. And when I say "literally", I mean that literally. You can ask for the contents of any of the departure boards around the city and you can see what is displayed on it, down to the memo line across the bottom (apologies to anybody who is lost by this description; take a trip to Manchester and you'll understand). For stations with multiple platforms (like St Peter's Square), there are multiple boards you can access. In fact, almost all stations have at least two boards - one for each direction. And each board has up to three trams on it. So how much information you can obtain about the network depends on how many lines go through a station.

I have considered a number of times whether it is possible to reverse engineer the data that they give you and build a complete map of the network including where all the trams are right now (by seeing how far each tram is from various stations along its line). I have also considered recently whether it would be possible to get an AI to do this. But it has never seemed worth the effort.

I have checked in a sample of the PID data you get back as samples/metrolink.json. As far as I can tell, this is just a json object wrapped around a value field which is an array of 231 PID displays. Here is one of these (chosen not quite at random):
{
      "Id": 1309608672,
      "Line": "South Manchester",
      "TLAREF": "FIR",
      "PIDREF": "FIR-TPID02",
      "StationLocation": "Firswood",
      "AtcoCode": "9400ZZMAFIR1",
      "Direction": "Incoming",
      "Dest0": "Victoria",
      "Carriages0": "Single",
      "Status0": "Due",
      "Wait0": "6",
      "Dest1": "Rochdale Town Centre",
      "Carriages1": "Single",
      "Status1": "Due",
      "Wait1": "10",
      "Dest2": "Victoria",
      "Carriages2": "Single",
      "Status2": "Due",
      "Wait2": "16",
      "Dest3": "",
      "Carriages3": "",
      "Status3": "",
      "Wait3": "",
      "MessageBoard": "On Tuesday 17th September Manchester United welcome Barnsley to Old Trafford. Kick Off is 8pm and services will be busier tha
n usual. Please allow extra time for travel.",
      "LastUpdated": "2024-09-17T19:52:55Z"
},
This is the departure board the the Incoming platform at Firswood station, which is on the East Didsbury-Rochdale and Manchester Airport-Victoria lines. The board shows up to four trams coming along: 0, 1, 2 and 3. Tram 0 is going to Victoria, has just one unit and is due in six minutes. So if I'm interested in trams from Firswood going to Victoria, I would expect that my server would query this, process it, and return something like the following:
{
  "Firswood": {
    "Victoria": [ "19:59", "20:09" ]
  }
}
And then the watch will be expected to convert that into a user-friendly display.

Let's Get Started

As I write this, I have done quite a bit of reading about ConnectIQ and Monkey C, and I think I know what I'm doing. But apart from my small foray into it seven years ago have no practical experience. Some of the decisions I have already "made" have been influenced by that "small amount of knowledge". I am assuming I want to do as little as possible on the watch, but for now I don't want to involve the phone more than necessary (i.e. I don't want to write a specific app on the phone), so I want to have the watch communicate directly with my web server and then have that pull back the data from TfGM's servers.

So I'm going to start by writing some PHP code to do that, and then write the code for a widget in Monkey C.

Let's get started!

No comments:

Post a Comment