|
| 1 | +""" |
| 2 | +About: List MF4 log files by period using the CANedge Python API |
| 3 | +and concatenate them into 'combined' MF4 files using the asammdf Python API. |
| 4 | +Optionally use TntDrive to map S3 server as local drive to work with S3 directly: |
| 5 | +https://canlogger.csselectronics.com/canedge-getting-started/transfer-data/server-tools/other-s3-tools/ |
| 6 | +""" |
| 7 | +import canedge_browser |
| 8 | +from asammdf import MDF |
| 9 | +from datetime import datetime, timezone, timedelta |
| 10 | +from pathlib import Path |
| 11 | +from concatenate_utils import extract_mdf_start_stop_time, hour_rounder |
| 12 | +import sys |
| 13 | + |
| 14 | + |
| 15 | +# specify input path for MF4 files (e.g. "D:/LOG" for SD, "Z:" for mapped S3 bucket, ...) |
| 16 | +input_root = Path("Z:") |
| 17 | + |
| 18 | +# specify output path (e.g. other mapped S3 bucket, local disk, ...) |
| 19 | +output_root = Path("C:/concatenate-mf4-by-period") |
| 20 | + |
| 21 | +# specify which period you wish to process and the max period length of each concatenated log file |
| 22 | +period_start = datetime(year=2022, month=12, day=12, hour=2, tzinfo=timezone.utc) |
| 23 | +period_stop = datetime(year=2022, month=12, day=16, hour=2, tzinfo=timezone.utc) |
| 24 | +file_length_hours = 24 |
| 25 | + |
| 26 | +# specify devices to process (from the input_root folder) |
| 27 | +devices = ["2F6913DB", "00000000"] |
| 28 | + |
| 29 | +# optionally DBC decode the data |
| 30 | +dbc_path = input_root / "dbc_files" |
| 31 | +dbc_files = {"CAN": [(dbc, 0) for dbc in list(dbc_path.glob("*" + ".DBC"))]} |
| 32 | +enable_dbc_decoding = False |
| 33 | + |
| 34 | +# ---------------------------------------- |
| 35 | +fs = canedge_browser.LocalFileSystem(base_path=input_root) |
| 36 | + |
| 37 | +for device in devices: |
| 38 | + cnt_input_files = 0 |
| 39 | + cnt_output_files = 0 |
| 40 | + cnt_sub_period = 0 |
| 41 | + sub_period_start = period_start |
| 42 | + sub_period_stop = period_start |
| 43 | + files_to_skip = [] |
| 44 | + |
| 45 | + log_files_total = canedge_browser.get_log_files(fs, device, start_date=period_start,stop_date=period_stop) |
| 46 | + log_files_total = [Path(input_root,log_file) for log_file in log_files_total] |
| 47 | + print(f"\n-----------\nProcessing device {device} | sub period length: {file_length_hours} hours | start: {period_start} | stop: {period_stop} | {len(log_files_total)} log file(s): ",log_files_total) |
| 48 | + |
| 49 | + # check whether to update sub_period_start to equal 2nd log file start for efficiency |
| 50 | + mdf = MDF(log_files_total[0]) |
| 51 | + mdf_start, mdf_stop = extract_mdf_start_stop_time(mdf) |
| 52 | + |
| 53 | + if mdf_stop < sub_period_start: |
| 54 | + print("First log file is before period start (skip): ", log_files_total[0]) |
| 55 | + files_to_skip.append(log_files_total[0]) |
| 56 | + if len(log_files_total) == 1: |
| 57 | + continue |
| 58 | + elif len(log_files_total) > 1: |
| 59 | + mdf = MDF(log_files_total[1]) |
| 60 | + mdf_start, mdf_stop = extract_mdf_start_stop_time(mdf) |
| 61 | + sub_period_start = hour_rounder(mdf_start) |
| 62 | + print(f"Period start updated to {sub_period_start}") |
| 63 | + |
| 64 | + # process each sub period for the device |
| 65 | + while sub_period_stop <= period_stop: |
| 66 | + cnt_sub_period += 1 |
| 67 | + sub_period_stop = sub_period_start + timedelta(hours=file_length_hours) |
| 68 | + |
| 69 | + # list log files for the sub period |
| 70 | + log_files = canedge_browser.get_log_files(fs, device, start_date=sub_period_start,stop_date=sub_period_stop) |
| 71 | + log_files = [Path(input_root,log_file) for log_file in log_files] |
| 72 | + log_files = [log_file for log_file in log_files if log_file not in files_to_skip] |
| 73 | + if len(log_files) > 0: |
| 74 | + print(f"\n- Sub period #{cnt_sub_period} \t\t\t| start: {sub_period_start} | stop: {sub_period_stop} | {len(log_files)} log file(s): ", log_files) |
| 75 | + |
| 76 | + if len(log_files) == 0: |
| 77 | + sub_period_start = sub_period_stop |
| 78 | + continue |
| 79 | + |
| 80 | + # concatenate all sub period files and identify the delta sec to start/stop |
| 81 | + mdf = MDF.concatenate(log_files) |
| 82 | + mdf_start, mdf_stop = extract_mdf_start_stop_time(mdf) |
| 83 | + mdf_header_start = mdf.header.start_time |
| 84 | + start_delta = (sub_period_start - mdf_header_start).total_seconds() |
| 85 | + stop_delta = (sub_period_stop - mdf_header_start).total_seconds() |
| 86 | + print(f"- Concatenated MF4 created (pre cut)\t| start: {mdf_start} | stop: {mdf_stop}") |
| 87 | + |
| 88 | + # cut the log file to only include intended period |
| 89 | + mdf = mdf.cut(start=start_delta, stop=stop_delta, whence=0,include_ends=False, time_from_zero=False) |
| 90 | + mdf_start, mdf_stop = extract_mdf_start_stop_time(mdf) |
| 91 | + |
| 92 | + # convert the start/stop time to string format for file-saving |
| 93 | + mdf_start_str = mdf_start.strftime(f"%y%m%d-%H%M") |
| 94 | + mdf_stop_str = mdf_stop.strftime(f"%y%m%d-%H%M") |
| 95 | + output_file_name = f"{device}/{mdf_start_str}-to-{mdf_stop_str}.MF4" |
| 96 | + output_path = output_root / output_file_name |
| 97 | + |
| 98 | + # DBC decode the data before saving |
| 99 | + if enable_dbc_decoding: |
| 100 | + mdf = mdf.extract_bus_logging(dbc_files) |
| 101 | + |
| 102 | + # save the cut MF4 to local disk |
| 103 | + mdf.save(output_path, overwrite=True) |
| 104 | + print(f"- Concatenated MF4 saved (cut)\t\t| start: {mdf_start} | stop: {mdf_stop} | {output_path}") |
| 105 | + |
| 106 | + cnt_output_files += 1 |
| 107 | + sub_period_start = sub_period_stop |
| 108 | + |
| 109 | + # check if the last log file is fully within sub period (i.e. skip it during next cycle) |
| 110 | + if mdf_stop < sub_period_stop: |
| 111 | + files_to_skip.append(log_files[-1]) |
| 112 | + |
| 113 | + if log_files[-1] == log_files_total[-1]: |
| 114 | + print(f"- Completed processing device {device}") |
| 115 | + break |
0 commit comments