#!/usr/bin/python import sys boltFieldNames = [ "BACKLIGHT", "AUTO_UPLOAD_ENABLED", "BOLT_BATTERY", "INVALID", "LED_MODE", "TOP_LED_WORKOUT", "TOP_LED_NOTIF", "TOP_LED_NAV", "BUZZ_WORKOUT", "BUZZ_NOTIF", "BUZZ_NAV", "BUZZ_MARIO", "AUTO_PAUSE_SPEED", "ALERT_PHONE", "ALERT_MSG", "ALERT_EMAIL", "DND_END_TIME_OLD", "AUTO_SHUTDOWN_DURATION", "MUTE", "BOLTAPP_VERSION", "KICKR_MODE_OVERRIDE_ALLOWED", "LED_MODE_OVERRIDE", "ZOOM_MIN_LEVEL", "FIRST_RUN_STATE", "AUTO_LAP_MODE", "AUTO_LAP_DISTANCE_M", "AUTO_LAP_DURATION_SEC", "BACKLIGHT_DURATION_SEC", "UPGRADE_STATE", "UPGRADE_DOWNLOAD_PERCENT", "DND_END_TIME", "DND_INTERVAL", "FOLLOW_WITH_HEADING", "SEGMENTS_ENABLED", "SEGMENTS_AUTO_PAGE_CHANGE", "SEGMENTS_NOTIF_ON_OTHER_PAGES", "SEGMENTS_LEDS", "SEGMENTS_MUTED", "WORKOUT_TYPE", "USER_OUTDOOR_MODE", "INVALID", "PAGE_DEFN", "WIFI_NW_COUNT", "INCLUDE_ZERO_IN_AVG_CADENCE", "PLANS_NOTIF_ON_OTHER_PAGES", "PLANS_BUZZER", "PLANS_LEDS", "SEGMENTS_DURING_PLAN", "PLAN_AUTO_LAP_ON_INTERVAL", "INCLUDE_ZERO_IN_AVG_POWER", "CFG_PROFILE_IDS", "CURRENT_CFG_PROFILE_ID", "CFG_PROFILE_NAME", "CFG_DISPLAY_CFG", "THEME", "INVALID", "AUTO_UPDATE", "WATCHFACE_CFG", "POOL_LENGTH_CM", "THEME_TINT_COLOR", "LOCATION_LAT", "LOCATION_LON", "INVALID", "CFG_PROFILE_ID_NEXT", "GPS_OVERRIDE", "ENABLE_OPTICAL_HR", "APP_PROFILE", "RUNNING_MODE", "RACE_LENGTH", "TRACK_LENGTH", "LAST_INTERACTION_TIME_SEC", "POOL_LENGTH_CUSTOM_CM", "AUTO_INT_ENABLED", "POOL_LENGTH_CUSTOM_METRIC", "BOLT_BATTERY_STATUS", "BOLT_SERIAL_NUMBER", "AUTO_RE_ROUT", "INVALID", "INVALID", "SOUNDS_CFG", "VIBRATIONS_CFG", "DND_SCHEDULE_START_TIME_MIN", "DND_SCHEDULE_END_TIME_MIN", "DND_CFG", "RADAR_CFG", "PMS_CFG", "PMS_SESSION_INDEX", "BODY_POSITION", "SPECIALIZED_AUTH_KEY", "SPECIALIZED_STAGING_URL", "GPS_POS_ASSIST_VALID", "UPGRADE_STATE_ROM", "BOLTAPP_WSM_ENABLED", "LOG_LEVEL", "RELEASE_CHANNEL", "247_MASK", "GPS_POS_ASSIST_DATA_REQ", "WORKOUT_PAGE_COLOUR_MODE", "ENABLED_CLM_TYPES", "BUTTON_SQUEEZE_CFG", "DISPLAY_CFG_HIDE_TITLES", "WORK_REST_CFG", "WORK_REST_AUTO_LAP_ON_INTERVAL", "PLAN_SYNC_STATUS", "RACE_RUNNING_CFG", "WORKOUT_PINNED_PAGE_ID", "CUSTOM_ALERTS_ENABLED", "CUSTOM_ALERTS_PLANNED_WORKOUT_ENABLED", "LEDS_MASK", "SUPERSAPIENS_ALLOWED", "SUPERSAPIENS_STATE", "ROUTE_SYNC_STATUS", "INVALID", "INVALID", "SYNC_REQ_MASK", "CLIMB_CFG", "NEXT" ] compFieldNames = [ "INVALID", "HEIGHT_CM", "WEIGHT_HG", "DOB", "MALE", "METRIC_SPEED_DISTANCE", "LOCALE", "POWER_FTP", "HR_RESTING", "HR_ZONE_1_CEIL", "HR_ZONE_2_CEIL", "HR_ZONE_3_CEIL", "HR_ZONE_4_CEIL", "HR_MAX", "INVALID", "LIFESTYLE", "INVALID", "TIME_FMT", "INVALID", "HR_BURN_RATE", "HR_BURST_RATE", "INVALID", "PHONE_BATTERY", "BOLT_TIME", "BOLT_TIME_ZONE", "SPD_ZONE_1_CEIL", "SPD_ZONE_2_CEIL", "SPD_ZONE_3_CEIL", "SPD_ZONE_4_CEIL", "PWR_ZONE_1_CEIL", "PWR_ZONE_2_CEIL", "PWR_ZONE_3_CEIL", "PWR_ZONE_4_CEIL", "INVALID", "USER_PROFILE", "METRIC_ELEVATION", "METRIC_TEMPERATURE", "METRIC_WEIGHT", "PWR_ZONE_5_CEIL", "PWR_ZONE_6_CEIL", "PWR_ZONE_7_CEIL", "INVALID", "PWR_ZONE_COUNT", "BOLT_TIME_INFO", "LOG_LEVEL", "FIRST_DAY_OF_WEEK", "AUTO_UPLOAD_MASK", "TARGET_DAILY_STEPS", "TARGET_WEEKLY_BIKE_DISTANCE", "TARGET_DAILY_CALORIES", "TARGET_WEEKLY_RUN_DISTANCE", "TARGET_WEEKLY_SWIM_DISTANCE", "TARGET_WEEKLY_CALORIES", "TARGET_WEEKLY_ACTIVE_TIME","CALIBRATION_RUN", "LOCATION_LAT", "LOCATION_LON", "PAIRED_ELEMNT_CFG", "247_WEEK_SUMMARY", "INVALID", "GENDER", "AUTO_RIVAL_DATA_BROADCAST", "LOCATION", "INVALID", "POWER_RUN_CRITICAL_POWER", "CLOUD_USER_ID", "CLOUD_SERVER_TYPE", "WAHOO_CLOUD_SUBSCRIPTION_STAT", "NEXT" ] def readfile(name): version = 0x00 profileID = 0xFFFF dataFields = {} timestampFields = {} with open(name, 'rb') as f: version = int.from_bytes(f.read(1), byteorder='little') profileID = int.from_bytes(f.read(2), byteorder='little') fieldCount = int.from_bytes(f.read(2), byteorder='little') for _ in range(fieldCount): fieldID = None if version == 0x00: fieldID = int.from_bytes(f.read(2), byteorder='little') elif version == 0x01: fieldID = int.from_bytes(f.read(4), byteorder='little') dataSize = int.from_bytes(f.read(2), byteorder='little') dataFields[fieldID] = f.read(dataSize) return { "version": version, "profileID": profileID, "dataFields": dataFields, "timestampFields":timestampFields } def main(name): config = readfile(name) fieldNames = [] if name.startswith('comp'): fieldNames = compFieldNames elif name.startswith('bolt'): fieldNames = boltFieldNames print(f'Version: {config["version"]}') print(f'Profile ID: {config["profileID"]}') for fieldID, fieldData in config["dataFields"].items(): print(f'{fieldID:3d} {fieldNames[fieldID]:32s} => {fieldData}') if __name__ == "__main__": if len(sys.argv) != 2 or sys.argv[1][:4] not in ['comp', 'bolt']: print('Usage: python elemnt-config.py comp.cfg or bolt-65535.cfg') sys.exit(0) main(sys.argv[1])