ADIF → 「HAM交信サポート(csv)」変換ツール
移動運用時にログ情報(交信記録)を持ち運べるようiPhone用のログアプリ「HAM交信サポート」を利用するようになりました。又、ホームでFT8の交信を行うようになってからパソコン用ログアプリはファイルメーカーPro(テンプレート利用)からRUMlogNGへ変更しました。(この時は他の局長さんが作成されたPerlのスクリプトでデータ移行は助かりました。)ログ用アプリが2種類になれば同期をどうするか?が課題になってきます。交信局数が少ないのでiPhoneへ手入力していましたが、忘れていると入力する局数が溜まったり、入力ミス(年齢が増えると集中力は反比例で下がってしまう。)も発生してきました。
ということでRUMlogNGのログ書き出し形式はadif、「HAM交信サポート」のインポート用ファイルの形式がcsv、それらの形式と諸々を整合する変換スクリプトをChatGPTに問合せてキャッチボール(やりとり)しながら変換ツールを作成してもらいました。忘れたらここを見てなるほどと思えればいいなと思い記しました。
機能
RUMlogNGで書き出したログファイルADIF形式を「HAM交信サポート」のインポートファイル形式csvへ変換して「HAM交信サポート」へ読込が出来ること。

ユーザーフィールド含む赤枠を対象にしました。自局のQTHはQSO Noteに入力しています。
Date Timeは日本時間です。
変換結果
変換したcsv形式のファイルをエクセル等の表計算アプリで開いた状態を再現しています。(RUMlogNGは自局コールサイン(Jx1xxx、Jx1xxx/P、Jx1xxx/6など)の入力欄が無いため、HAM交信サポートのMy Callsignへどう反映するか?が課題として残っています。)
| Callsign | Portable | Time | Time End | RST Sent | RST Received | Who Called | Grid Zone | My Callsign | My QTH | Other QTH | Other Latitude | Other Longitude | Distance | Other Name | Band | Frequency | Mode | QSL Card | QSL Way | QSL Flag | NR Received | NR Sent | Rig Model | Antenna | TXPower | Latitude | Longitude | JCC/JGC | Altitude above sea level | Weather | Other Information | QSL Comment | Contest Name | Contest Points |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| JC1ABC | 2026-03-26 10:14:00 +0900 | 2026-03-26 10:14:00 +0900 | 55 | 55 | PM96VQ | 宇都宮市JCC#1501 TG-109寅巳山PM96vq | 埼玉県大宮市 PM96pe | あいう | 2m | 144.205000 | SSB | IC-705 | 2mHRH770 | 5 | JA/TG-109寅巳山 | |||||||||||||||||||
| JC1DEF | 1 | 2026-03-26 10:11:00 +0900 | 2026-03-26 10:11:00 +0900 | 59 | 59 | PM96VQ | 宇都宮市JCC#1501 TG-109寅巳山PM96vq | 茨城県日立市 QM06bg JA/IB-006足尾山 | かきく | 2m | 144.205000 | SSB | IC-705 | 2mHRH770 | 10 | JA/TG-109寅巳山 |
ファイルをiPhoneの「HAM交信サポート」に読込み(iPhoneの画面キャプチャ)

相手のQTHはRUMlogNGのQTH + Your Locater + Your SOTA Ref.を連結しています。
無線機種類とアンテナはRUMlogNGのRIG/ANTを/で分割しています。
スクリプト作成時の環境
・OS :macOS 15.7.3
・Hard:Mac Book Air M4 2025
・pythonがインストールしてあること
スクリプトの動作手順
1.準備するファイル(拡張子を間違えなければファイル名は任意と思います。)
RUMlogNGからADIF形式で書き出したファイル名:input.adi
スクリプトのファイル名 :adi_to_csv_script.py
2.ターミナルから

(例:cd Desktop はデスクトップへ移動)
スクリプトを動かすため、python3 + スペース + ファイル名を入力してエンターします。
スクリプトが起動して、ADIF → HAM交信サポート変換ツールダイアログが表示されます。
(間違った場合はNo Such…見つからないよ みたいなメッセージがターミナルに出ます。コピペしてChatGPTに質問すると回答が得られます。)

ファイルを選ぶダイアログが表示されます。該当ファイルを選択してOpenをクリックします。

error messagingが出る場合も気にせず進めます。(ChatGPTによると重大なエラーではないそうです。)


(例:output260326-3 )Format:CSV files(.csv)を確認してSaveをクリックします。


スクリプト(adi_to_csv_script.py)
ChatGPTで出来たコードをコピー&Visual Studio Code(Mac用アプリ)へペースト ファイル名をadi_to_csv_script.pyで保存しました。
import re
import csv
import tkinter as tk
from tkinter import filedialog, messagebox
#—————————–
#ADIF解析
#—————————–
def parse_adif(file_path):
with open(file_path, ‘r’, encoding=’utf-8′, errors=’ignore’) as f:
content = f.read()
if '<EOH>' in content:
content = content.split('<EOH>')[1]
content = content.replace('\n', ' ').replace('\r', ' ')
records = re.split(r'<eor>', content, flags=re.IGNORECASE)
parsed_data = []
for record in records:
fields = re.findall(r'<(.*?):(\d+).*?>([^<]*)', record)
record_dict = {}
for field_name, length, value in fields:
record_dict[field_name.upper()] = value.strip()
if record_dict:
parsed_data.append(record_dict)
return parsed_data
#—————————–
#日時整形
#—————————–
def format_datetime(date, time):
if date and time and len(date) == 8 and len(time) == 6:
d = f”{date[0:4]}-{date[4:6]}-{date[6:8]}”
t = f”{time[0:2]}:{time[2:4]}:{time[4:6]}”
return f”{d} {t} +0900″
return “”
#—————————–
#Callsign解析
#—————————–
def parse_callsign(call):
base = call
portable = “”
info = “”
if "/" in call:
parts = call.split("/")
base = parts[0]
suffix = parts[1].upper()
if suffix.isdigit():
portable = suffix
elif suffix == "P":
portable = "P"
elif suffix == "MM":
info = "Maritime Mobile"
elif suffix == "QRP":
info = "QRP"
else:
info = suffix
return base, portable, info
#—————————–
#CSV出力
#—————————–
def write_log_csv(data, output_file):
output_order = [
'Callsign', 'Portable', 'Time', 'Time End', 'RST Sent', 'RST Received',
'Who Called', 'Grid Zone', 'My Callsign', 'My QTH', 'Other QTH',
'Other Latitude', 'Other Longitude', 'Distance', 'Other Name',
'Band', 'Frequency', 'Mode', 'QSL Card', 'QSL Way', 'QSL Flag',
'NR Received', 'NR Sent', 'Rig Model', 'Antenna', 'TXPower',
'Latitude', 'Longitude', 'JCC/JGC', 'Altitude above sea level',
'Weather', 'Other Information', 'QSL Comment',
'Contest Name', 'Contest Points'
]
mapping = {
"RST_SENT": "RST Sent",
"RST_RCVD": "RST Received",
"GRIDSQUARE": "Grid Zone",
"NAME": "Other Name",
"QTH": "Other QTH",
"BAND": "Band",
"FREQ": "Frequency",
"MODE": "Mode",
"TX_PWR": "TXPower"
}
with open(output_file, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(output_order)
for row in data:
new_row = []
raw_call = row.get("CALL", "")
callsign, portable, extra_info = parse_callsign(raw_call)
for field in output_order:
value = ""
if field == "Callsign":
value = callsign
elif field == "Portable":
value = portable
elif field == "Time":
value = format_datetime(row.get("QSO_DATE", ""), row.get("TIME_ON", ""))
elif field == "Time End":
value = format_datetime(row.get("QSO_DATE", ""), row.get("TIME_OFF", ""))
elif field == "Other Information":
value = extra_info
else:
adif_key = next((k for k, v in mapping.items() if v == field), None)
value = row.get(adif_key, "") if adif_key else ""
new_row.append(value)
writer.writerow(new_row)
#—————————–
#GUI処理
#—————————–
def select_file():
file_path = filedialog.askopenfilename(filetypes=[(“ADIF files”, “*.adi”)])
entry_input.delete(0, tk.END)
entry_input.insert(0, file_path)
def convert():
input_file = entry_input.get()
if not input_file:
messagebox.showerror("エラー", "ファイルを選択してください")
return
output_file = filedialog.asksaveasfilename(defaultextension=".csv",
filetypes=[("CSV files", "*.csv")])
if not output_file:
return
try:
data = parse_adif(input_file)
write_log_csv(data, output_file)
messagebox.showinfo("完了", "変換が完了しました!")
except Exception as e:
messagebox.showerror("エラー", str(e))
#—————————–
#GUI画面
#—————————–
root = tk.Tk()
root.title(“ADIF → HAM交信サポート変換ツール”)
frame = tk.Frame(root, padx=10, pady=10)
frame.pack()
tk.Label(frame, text=”ADIFファイル:”).grid(row=0, column=0)
entry_input = tk.Entry(frame, width=40)
entry_input.grid(row=0, column=1)
tk.Button(frame, text=”参照”, command=select_file).grid(row=0, column=2)
tk.Button(frame, text=”変換実行”, command=convert, bg=”lightblue”).grid(row=1, column=1, pady=10)
root.mainloop()
以上です。
応用編としてSOTAデータベースアップロード用も出来るのか?気になるところです。