Activity Profile Editor for Edge devices

For anyone who is interested in editing their Edge Activity Profile screens and data fields on a computer vs the Edge itself, this project may have what you’re looking for.

github.com/.../Activity-Profile-Editor

I only have my own Edge 530 to test this on, so the confirmed field IDs, screen types, and layouts are all specific to that model. The underlying screen/data-field mechanism looks like it should carry over to other Edge models (same 10-field-per-screen cap, same layout patterns from what I've seen in demo videos of newer touchscreen units), but that's unconfirmed without real testing on other hardware — collaboration from anyone with a different model would help a lot.

You can use it in purely a “read” mode, if you just want to start by viewing existing Profiles. The deploy method pushes the changed Activity Profile .fit files through the NewFiles folder/restart mechanism to make the changes.

Here are the features that are built into this toolkit and available in the GUI:

- Cloning - copy an existing Activity Profile to a new name
- Backups - backup Activity Profiles to local disk
- Backup pruning - delete old backups
- Change Screens - Add/Delete/Reorder/Hide/Layout-A/B
- Data Fields - Add/Delete/reorder/Modify the data fields within a screen
- Startup.txt - Change the text and display timer value of the boot message file
- Restore - restore a deleted Activity Profile if it was previously backed up by the toolkit
- Import - import a Profile from another source or other backup method
- Favorite - save a favorite screen to reuse/transfer to another Activity Profile

The code is written in Python and uses the garmin-fit-sdk and wxPython for the GUI interface.
There are also a set of CLI tools, in case you want to use individual aspects of the toolkit without the gui_app.
The tools are compatible with MacOS (tested on MacOS 26) as well as Windows (tested on Win 11). It was also tested on Win 10 - 32-bit, but requires manual install of the Python version and the wxPython (see the README for those details).

Here’s a sample output of a couple of the cli tools:

% python3 garmin_device.py detect
Garmin device found at: /Volumes/GARMIN/Garmin
Manufacturer: garmin
Product: edge_530
Serial: xxxxxxxxxxxx <-not showing my actual serial number
Software version: 981

% python3 fit_dump.py screens ./CyclingRoadClonebox.fit
=== Screen order as viewed on-device (reorderable via --swap-order) ===
POS SLOT CNT LAY FLAG TYPE FIELDS
1 [ 0] 6f B Screen 1 Timer, Distance, Power Graph, Heart Rate Graph, Speed, Cadence
2 [ 7] 7f Screen 4 Cadence Bars, Lap HR, Last Lap HR, EPOC, HR Zone Graph, Anaerobic TE, Aerobic Training Effect
3 [ 1] 2f Lap Summary Speed, Distance
4 [ 2] 0f Map
5 [ 3] 2f Elevation Percent Grade, Elevation (ft)
6 [ 8] 0f GroupTrack List
7 [13] 2f ClimbPro Percent Grade, Elevation (ft)
8 [ 4] 2f Cycling Dynamics Power Phase Left, Power Phase Right
9 [ 5] 2f Screen 2 Avg Speed, HR Zone Graph
10 [ 6] 7f Screen 3 Speed Bars, HR Bars, Power Bars, Lap HR, Last Lap HR, Lap %Max HR, Respiration
11 [ 9] 10f Screen 5 Lap Time, Odometer, Dest. Ahead, Lap Dist., Last Lap Speed, Total Descent, Lap Speed, Reps to Go, Lap Power, Target Power
12 [11] 10f Screen 6 Target, Step Time, Duration, Workout Comparison, Workout Step, Light Battery, Last Lap Power, Lap Time Standing, Lap Time Seated, Avg PCO

Also, here's a few screen shots of the GUI

  • NOTE: SEE UPDATE IN MY REPLY BELOW

    Ran into an interesting issue while testing this toolkit against my Edge 530: a Connect IQ third-party data field (WindField, in my case) that I'd set via the GUI on a screen showed up correctly in the file and in the toolkit's own display — but rendered as Timer on the actual device. Same thing happened whether I moved it to a full-width slot, a half-width slot, a brand new screen, or a completely fresh test profile.

    After a lot of byte-level comparison between working and broken files, a few things came out clearly:

    • Every Connect IQ field gets assigned a plain numeric ID by the device itself — not by the app's author. WindField's author (Scott Beam) confirmed this directly: "that's not anything I set in the code at all." He also commented that Timer may be Garmin's generic fallback whenever a Connect IQ reference can't be resolved, which matched exactly what I was seeing.

    • That numeric ID isn't tied to one specific app. I installed a second, completely free Connect IQ app on the same device, and the device reused the same numeric ID that had previously meant WindField — now pointing at the new app instead. Since one of the two apps involved has no license or subscription at all, this rules out any licensing/anti-piracy explanation; it's just how Garmin links a screen slot to whichever Connect IQ app is currently installed in that device-assigned slot.

    • The file itself doesn't carry enough information to recreate this link. There's a secondary record in the file that looks like a per-app identifier, but it's constant no matter where the field is placed, and copying it byte-for-byte into a fresh slot didn't make it work. Whatever actually resolves a Connect IQ field must be checked against state that lives on the device, not in the file.

    Practical result: this toolkit can reliably preserve a working Connect IQ field placement (cloning a whole profile that already has one) and can relocate it within a profile that already has it working — but it can't originate one in a slot that's never had it. Only Garmin's own on-device editor can do that first placement, at least with the testing I've done.

    As of the latest version, the toolkit reflects this directly: any field ID known to behave this way now shows up as a generic "CIQ Data Field" (rather than guessing at a specific app name that might be wrong on someone else's device), and both the CLI and GUI refuse outright to try writing one into a new slot — rather than silently producing a file that looks right and doesn't work.

    Full write-up with the byte-level detail is on github in PROJECT_NOTES.md (Doc rev 95–97) if anyone wants to dig further or has a different device/app combination to compare against.

  • Follow up on my earlier reply in this thread, where I quoted my project's
    README describing a hard limitation around Connect IQ data fields. That
    limitation turned out not to be a limitation at all — it was a message my
    tooling wasn't writing. Posting the details here since it's undocumented in
    the published FIT Profile and may save someone else the same investigation.

    **Background.** Activity Profile files contain screen definitions where each
    screen carries an array of numeric data field IDs. Third-party Connect IQ
    data fields appear in that array as ID **216**. Editing such a file directly —
    reordering fields, adding or removing fields on a screen holding a Connect IQ
    field — produced a file that looked correct by every measure I could apply,
    but rendered the Connect IQ field as **"Timer"** on the device. Whole-profile
    copies preserved it; any structural edit broke it. I had concluded the linkage
    was device-side and unreachable from the file.

    **That was wrong.** The linkage is in the file, in **`mesg_num` 170** — which
    is absent from the published `Profile['mesg_num']` table entirely (I checked
    directly against `garmin_fit_sdk` 21.213.0; there are gaps at 163, 166, 168,
    170–173, 175–176).

    **The record.** One message-170 record exists per Connect IQ app used by the
    profile, with exactly two fields:

    | field | size | meaning |
    |---|---|---|
    | 1 | 16 × `uint8` | the Connect IQ app's UUID, big-endian |
    | 2 | 5 × `uint32` (20 bytes) | packed placement list, see below |

    **Field 2** is a packed array of **15-bit entries**, one per placement of that
    app within the profile:

    ```
    bits 0-4 screen slot index (the screen record's message_index), 0-31
    bits 5-14 ONE-HOT field position within that screen, bit (5 + position)

    entry = (1 << (5 + field_position)) | slot_index
    ```

    Entries are stored in ascending slot order. Five slot bits (31 slots) plus ten
    position bits (the ten-field-per-screen maximum) is exactly 15.

    **Field ID 216 is not an app identity.** This was the part that misled me
    longest. 216 is a generic "a Connect IQ data field goes here" marker — I
    confirmed this by placing two *different* Connect IQ apps on the same screen
    at adjacent positions, and both were stored as 216. All app identity lives in
    the message-170 records and nowhere else.

    **So the "Timer" behavior has a simple explanation.** 216 tells the device to
    render a Connect IQ field at that position. The device then looks for a
    message-170 entry claiming that exact (slot, position) pair. No matching
    entry, nothing resolves, and Garmin's generic Timer fallback renders instead.
    Every symptom I'd seen follows from that one rule: whole-profile copies work
    because the records come along unchanged; moving a *screen* in the display
    order works because that changes a different field and not the slot index;
    editing a screen's field layout breaks it because positions shift and the
    record doesn't follow.

    **Confirmed on hardware.** All of the following were verified by round-trip
    on a real Edge 530 — write the file, deploy, check the device:

    - moving a Connect IQ placement to a different position on the same screen
    - adding a placement to a screen that never had one
    - choosing *which* app renders, by choosing which record receives the entry
    - removing a placement cleanly, with no Timer left behind
    - a position shift occurring as a side effect of inserting a neighbouring
    field, which is the ordinary "add a field" case that broke originally

    The one case I have **not** yet tested is creating a message-170 record from
    scratch in a profile that carries none — i.e. adding a Connect IQ field to a
    profile that has never had that app. Everything above involves editing records
    the device itself wrote.

    **Credit:** the idea to look at this record's second field came from the
    author of Fit File Viewer (fitfileviewer.com), who had independently labeled
    170 as a Connect IQ record with a "field bits" column. That column is exactly
    bits 5–14 above.

    ---

    ## What this means in practice

    This is now implemented and working in my toolkit, so for anyone doing the
    same kind of file editing, here's where the line actually falls.

    **Works:** moving a Connect IQ field to a different position on a screen,
    adding or removing ordinary fields around one, removing a Connect IQ field
    outright, and reordering or deleting whole screens that hold one. All of it
    just requires updating that app's entry alongside the field-array write.

    **Doesn't work, and this is a property of the format rather than a choice:**
    adding a Connect IQ field where one isn't already. Since 216 is generic and
    carries no app identity, nothing in the edit says which app a new marker
    should resolve to — you'd be writing a field the device can't resolve, which
    renders as "Timer". Likewise, a screen holding *two* Connect IQ fields can't
    be safely rearranged from a field array alone: both are 216, so there's no
    way to tell which app ended up where afterward.

    **The practical workaround is cloning.** Place the Connect IQ fields once
    using the device's own editor, then copy that profile — the records come
    along byte-for-byte — and edit the copy freely. That gives you as many
    variants as you want off one on-device setup, which covers most of what you'd
    otherwise want a "place a Connect IQ field" feature for.

    One deliberate design note, in case it's useful to anyone building something
    similar: I kept the marker id as a small constant set and specifically did
    *not* build any table of known Connect IQ apps or their UUIDs. The tool
    describes the file format and never needs to know which apps exist, so there's
    no registry to keep current as people install new ones.

    Happy to answer questions if anyone's working in the same area.

  • New release available V1.4.0 - now has per-type layout model for Garmin's named special screens screens.

    Many of the special "named" screens had unique data field layouts and/or limitations that the initial release didn't take into consideration, but now does.

    Also, it would be interesting to see some screen dump outputs from other models. Those would be read-only which only touches the device filesystem to read the Profile. Once you would install the software the CLI cmd would be: %python3 fit_dump.py screens <path_to_garmin>/Sports/CyclingRoad<profilename>.fit.

  • I just found a major hurtle, I've been on the Edge 530 for so long that I hadn't realized that the next model lines had switched to the MTP method for connectivity (I see lots of discussions in the forums around that topic). The next best thing I can do is create an "offline" mode for the profile editor. That way if you pulled or copied a Profile from a device, you could view and change it, then export that file to copy back through whatever you use to read/write files to you Edge device.

  • The Offline mode is coming along, I have an Edge 840 for testing it and I'm using OpenMTP for Mac for now to test getting files to and from the 840. I should have a new release on github later this week.

  • I just released version 1.6.0, it supports offline mode and is now aware of not just the Edge 530 but also the Edge 840 and all of the user screen layouts and special screen configurations in the Activity Profiles. It's also aware of the additional data field types that are supported on the Edge 840. For editing Activity Profiles or the startup.txt file from an Edge 840 (on the Mac) I used the OpenMTP program to pull and push the edited files.

  • The README got an update along with a few data fields that had "collisions". The main think was describing the difference between the Connected (mass storage) mode and the Offline mode for Edge devices that use MTP. Here's a section of the README file with the relevant info:

    Which Edge models this works with — and the two ways to use it

    There are two workflows, and which one you get depends on how your Edge connects to a computer. Both end with an edited profile on the device; they differ in who moves the file.

    Connected mode Offline mode
    Your Edge appears as a normal disk — a volume under /Volumes on macOS, a drive letter on Windows a "Portable Device" (MTP), or nothing browsable on macOS
    Models Edge 530 (tested), and other USB Mass Storage Edges Edge 840 (tested); 540, 1040, 1050 reported as MTP
    The toolkit finds the device itself asks you for a folder of .fit profiles
    Writing the result Deploy — straight to the device Export — to a folder, then you copy it across
    Restore from backup yes no, it is a device operation — the panel points you at your backup folder instead

    Garmin moved the newer models to MTP (Media Transfer Protocol), which presents a firmware-controlled view rather than a real disk. Note this may have arrived on your device through a firmware update rather than being true when you bought it.

    Offline mode is not only for MTP devices — it is also how you edit a profile you pulled last week with nothing plugged in.

    Connected mode, step by step

    1. Plug the Edge in and wait for it to mount.
    2. Start the toolkit; it finds the device and lists your profiles.
    3. Edit screens, fields and layouts.
    4. Deploy. The toolkit backs up the original first, writes, then reads the file back and byte-compares before reporting success.
    5. Eject and restart the Edge.

    Offline mode, step by step

    Copy the profiles off the device with an MTP browser —
    1. Start the toolkit and choose "Work Without a Device…", then point it at that folder.
    2. Edit exactly as you would with a device attached — the profile list, screens view, editor, backups, Clone and the boot-message editor all work the same.
    3. Export. It writes to a folder you choose, reads the result back and byte-compares it, and checks the file's own CRC before writing anything at all.
    4. Copy the exported file into Garmin/NewFiles/ on the device and restart the Edge.

    The filename matters, and the dialog handles it for you. The Edge matches profiles by filename on import and silently ignores anything that does not match — no error, no indication, the profile simply does not change. The save dialog pre-fills the original filename. Change it anyway and you get a warning naming the consequence, not a refusal: exporting a spare copy under another name is legitimate.

    startup.txt, the custom boot message, works in both modes.

    Does the NewFiles/ route actually work over MTP?

    Yes — confirmed repeatedly on an Edge 840. An edited ROAD profile has gone through it end to end, including a Connect IQ field that survived three chained edits, and a screen using a layout variant the 530 does not offer, which the 840 rendered correctly. A hand-built Totals.fit written the same way was also accepted.

    This was an open question until v1.5.0 and is recorded here because earlier versions of this README asked readers to help answer it.