|
#!/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", "CONNECTION_INFO", "ACTIVITY_FEEDBACK_ENABLED", "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 'comp' in name: |
|
fieldNames = compFieldNames |
|
elif 'bolt' in name: |
|
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 ('comp' not in sys.argv[1] and 'bolt' not in sys.argv[1]): |
|
print('Usage: python elemnt-config.py comp.cfg or bolt-65535.cfg') |
|
sys.exit(0) |
|
|
|
main(sys.argv[1]) |