Skip to content

Instantly share code, notes, and snippets.

@pdelteil
Forked from Intyre/Wahoo_Elemnt.md
Created May 2, 2023 05:12
Show Gist options
  • Save pdelteil/e1e6e105074487298850d0df9350ddcd to your computer and use it in GitHub Desktop.
Save pdelteil/e1e6e105074487298850d0df9350ddcd to your computer and use it in GitHub Desktop.
Wahoo Elemnt - Tips, tricks and custom images

Wahoo Elemnt - Tips, tricks and custom images

TOC

Firmware

The latest firmware version can be downloaded as .apk from version.json. All devices use the same BoltApp.apk. The BoltApp gets started by another application BoltLauncher and can be found on the device at /system/priv-app/BoltLauncher/BoltLauncher.apk.

Another interesting endpoint is firmware.json (bolt, bolt2, roam).

Install with ADB

adb install -r BoltApp.apk

Changelogs

BoltLauncher

Known versions of BoltLauncher are:

Device Code Name Info
ELEMNT 63 1.2.0.3 boltlauncher
BOLT2 10 1.0.10 boltlauncher2

Enable ADB

Enable ADB mode by pressing the power button twice, disconnect and reconnect the USB cable. RUN adb devices to see if it works. This method works on the ELEMNT, BOLT, BOLT2 and ROAM.

Tips and Tricks

Config backup

The .zip contains bolt-65535.cfg, comp.cfg poi.cfg and routes.cfg

Create

# create special directory
adb shell mkdir /sdcard/config_backup

# save to pc
adb pull /sdcard/config_backup/elemnt_config.zip .

Restore

# create special directory
adb shell mkdir /sdcard/config_restore

# save to pc
adb push elemnt_config.zip /sdcard/config_restore/elemnt_config.zip

# reboot device
adb shell reboot

Custom maps

Open Street Map is the source of the map data. MapsForge is used to generate the maps. wahooMapsCreator is a project with scripts to create maps.

Custom routing

Generate routing tiles with Valhalla/Mjolnir. The BOLT2 supports elevation.

Custom images

I'm not sure if this is the correct way of creating a custom image. I expect something did go wrong while creating a system image, my device has around 200MB of free space with only 1 map.

Create backup images from the device

#!/bin/sh
# backup.sh
for i in boot system recovery nvdata ; do
  adb pull /dev/block/platform/mtk-msdc.0/by-name/$i elemnt-backup-$i.img
done
tar czf elemnt-backup.tar.gz *.img

Keep the backups somewhere safe, Wahoo does NOT provide images.

Extract elemnt-backup-system.img

tar xzf elemnt-backup.tar.gz elemnt-backup-system.img

Test system image

Recreate a system image without changing anything and flash it to make sure it works.

# create a system directory
mkdir -p system

# mount the system image
sudo mount -t ext4 -o loop elemnt-backup-system.img system/

# create new image
sudo ./android_img_repack_tools/make_ext4fs -s -l 504M -a system new-system.img system/

# umount system
sudo umount system/

Now flash the image.

Modify and create a new image

# create a system directory
mkdir -p system

# mount the system image
sudo mount -t ext4 -o loop elemnt-backup-system.img system/

Disable updates

Add bolt.wahoofitness.com to the hosts file:

sudo sh -c "echo '127.0.0.1 bolt.wahoofitness.com'" >> system/etc/hosts

Decode and resign

Create keystore

The default BoltApp.apk is included in the BoltLauncher.apk. To install a modified .apk later you need to resign them both. First create a keystore to resign the .apks with.

The keytool requires you to enter a password and some information (empty information works) and asks if the data is correct. Respond with yes. Give it another password for the cert and a keystore is generated.

keytool -genkey -keystore wahoo.keystore -alias cert -keyalg RSA -keysize 2048 -validity 10000
# decode BoltLauncher with apktool
apktool d -r -s system/priv-app/BoltLauncher/BoltLauncher.apk -o BoltLauncher

# remove all files from META-INF in the BoltApp.apk
zip -d BoltLauncher/assets/BoltApp.apk META-INF/CERT.RSA META-INF/CERT.SF META-INF/MANIFEST.MF

# resign BoltApp.apk
jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore wahoo.keystore BoltLauncher/assets/BoltApp.apk cert

# remove all files from META-INF in BoltLauncher
rm -fr BoltLauncher/original/META-INF/*

# rebuild BoltLauncher
apktool b BoltLauncher -o BoltLauncher.apk

# resign BoltLauncher.apk
jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore wahoo.keystore BoltLauncher.apk cert

# copy BoltLauncher back to the system image
sudo cp BoltLauncher.apk system/priv-app/BoltLauncher/BoltLauncher.apk

Remove some apps from system/app (I removed Camera2, Gallery2, Calendar2, Calendar and Calculator) to make it stop complaining about no space left.

#!/bin/sh
# remove-apps.sh
for i in Camera2 Gallery2 Calendar2 Calendar Calculator; do
  sudo rm -fr system/app/$i
done

Create image

sudo ./android_img_repack_tools/make_ext4fs -s -l 504M -a system new-system.img system/

Umount system

sudo umount system/

Flash system image

Unlock the bootloader

Don't do this, this will void the warranty (bootloader) warranty: no. Reboot the device with adb shell reboot bootloader. The screen goes blank and fastboot shoult start, test it with sudo fastboot devices. Unlock with sudo fastboot oem unlock and press the power button on the device to start unlocking. The output should be something like:

(bootloader) Start unlock flow
OKAY [  9.707s]

Check it with sudo fastboot getvar all, Unlocked: yes. Start the device with sudo fastboot continue

fastboot mode with ADB

Run adb shell reboot bootloader and test with sudo fastboot devices.

fastboot mode with Linux

Make sure the device is turned off, run the following script as with sudo fastboot.sh and connect the USB cable.

#!/bin/sh
# fastboot.sh
echo -n "Waiting for Elemnt"
until [ -c "/dev/ttyACM0" ]; do
  echo -n "."
  sleep 1
done

echo -n 'FASTBOOT' > /dev/ttyACM0
echo " Starting fastboot mode"
sleep 2
fastboot devices
# start fastboot
adb shell reboot bootloader

# flash the image
sudo fastboot flash system new-system.img

# remove old stuff
sudo fastboot format cache
sudo fastboot format userdata

# boot the device
sudo fastboot continue

Update BoltApp.apk

Check the version number and change the url.

System image
# download BoltApp.apk
wget -O BoltApp.apk https://cdn.wahooligan.com/wahoo-fwu/elemnt/boltapp/6922/BoltApp.apk

# remove META-INF files
zip -d BoltApp.apk META-INF/CERT.RSA META-INF/CERT.SF META-INF/MANIFEST.MF

# resign BoltApp.apk
jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore wahoo.keystore BoltApp.apk cert

# copy BoltApp.apk to BoltLauncher
cp BoltApp.apk BoltLauncher/assets/BoltApp.apk

Rebuild the BoltLauncher, copy the BoltLauncher.apk to the system and build a new system image.

ADB update

# download BoltApp.apk
wget -O BoltApp.apk https://cdn.wahooligan.com/wahoo-fwu/elemnt/boltapp/6922/BoltApp.apk

# remove META-INF files
zip -d BoltApp.apk META-INF/CERT.RSA META-INF/CERT.SF META-INF/MANIFEST.MF

# resign BoltApp.apk
jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore wahoo.keystore BoltApp.apk cert

# install with adb
adb install -r BoltApp.apk

Tools used

apktool

jarsigner, keytool

  • openjdk

make_ext4fs

git clone -b android-5.1.0 https://github.com/ASdev/android_img_repack_tools
cd android_img_repack_tools
./configure
make
cd ..

Thanks

Thanks to Team Wahoo for making this device! :D Joshua, from the blog posts to make me decide to buy the Elemnt.

Workout .plan

A .plan file contains 2 sections.

Add or delete .plan files to /sdcard/plans and resync the workouts.

Header

The section starts with =HEADER=.

Key Description
NAME Name of workout
DESCRIPTION Description for workout
DURATION Total duration in seconds
SCHEDULED Scheduled date in yyyyMMdd format
FTP_TEST_FACTOR ?
=HEADER=
NAME=Name 
DESCRIPTION=Description
# Duration: 5 mins
DURATION=300
SCHEDULED=20200428
FTP_TEST_FACTOR=0.90

Stream

The section starts with =STREAM=. The stream section has the interval data.

=STREAM=

=INTERVAL=
=INTERVAL=
=INTERVAL=
ect...

Interval / Subinterval

A interval starts with =INTERVAL=. Inside a interval section you can add one or more =SUBINTERVAL= sections to group them.

Key Description
INTERVAL_NAME Name of interval
INTERVAL_DESCRIPTION Description for interval
REPEAT Repeats the interval and/or subintervals x times
CAD_LO Cadence Low target
CAD_HI Cadence High target
HR_LO Hearth rate Low target
HR_HI Hearth rate High target
PWR_LO Power Low target
PWR_HI Power High target
SPD_LO Speed Low target
SPD_HI Speed High target
RPE_LO Rating of perceived exertion Low target
RPE_HI Rating of perceived exertion High target
PERCENT_FTP_LO FTP Low target
PERCENT_FTP_HI FTP High target
LEVEL Kickr Level
ERG Kickr ERG
=INTERVAL=
INTERVAL_NAME=Name
INTERVAL_DESCRIPTION=Description
REPEAT=2
CAD_LO=80
CAD_HI=100
HR_LO=130
HR_HI=140
PWR_LO=200
PWR_HI=220
SPD_LO=30
SPD_HI=40
RPE_LO=6
RPE_HI=20
PERCENT_FTP_LO=60
PERCENT_FTP_HI=60
# Duration: 5 mins
MESG_DURATION_SEC>=300?EXIT
MESG_DISTANCE_M>=1000?EXIT

There are two ways to set the interval length. Include it with the interval.

Key Description
MESG_DURATION_SEC Interval duration
MESG_DISTANCE_M Interval distance

Trick from INEOS_Climbing.v1.plan

=INTERVAL=
INTERVAL_NAME=CLIMB
# 15 mins @ 75-90%
# 10 second spike to 125% every 3 mins
REPEAT=4
PERCENT_FTP_LO=75
PERCENT_FTP_HI=90
MESG_DURATION_SEC>=180?EXIT
MESG_DURATION_SEC>=170?PERCENT_FTP_LO=125
MESG_DURATION_SEC>=170?PERCENT_FTP_HI=125
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment