Hamlife with Mac, JJ1XOF

MLDXでのQSLデータ面印刷への道(1) ログデータのCSV化

Macでログを管理する手段としてMacLoggerDXというソフトを使うことにした。

Macでのログ管理。ワテクシの場合。

ログ管理ソフトからは、QSLを印刷したくなるものだ。
某国内デファクトのログソフトでは、「素敵なQSLカード」印刷機能と称しているようだ。
しかし、素敵かどうかは、受け取った人の感性に委ねられるべきものではないだろうか。

それはさておき、MacLoggerDXにもQSLカード印刷機能はあるようだ。

ログ表示タブでQSOログを選択し、QSLのタブをクリックすると上記のような画面が表示される。
ここで、”Print QSL”をクリックしてみると、選択したQSOのログが突如PNGファイルとしてポップアップ表示される。
1ウインドウ1画像で。
1枚1枚印刷するのであれば使えなくはない機能にも思えるが、そんな使い方はしない。

100QSO分のQSLを印刷しよう!
と思ったら、ブラクラ(死語)にあったような状態になる。

やってらんない。

と、言う人のために、データ面に該当する内容のラベルを生成する機能もあるようだ。


ラベルシートの寸法や行数列数を選ぶと、ラベル印刷に適したイメージを生成してくれる、といった風情。

しかし、QSL印字データ生成も、ラベル生成も、それがあってもねえ…
ビューロ転送用のコールサイン枠はどうする?手書き?
QSO DateはUTCだし、メッセージも書けないし。

という問題点が残る。

そういうわけで、あれこれ料理してQSLカードのデータ面を印刷する仕組みを自力で構築したので、それについて何回かに分けてご紹介したい。

ざっくり言うと、こういう流れになった。

MacLoggerDX自体にも、ファイルのエクスポート機能はついてはいる。

これはこれで、多少は使える。
SOTA/POTAにアップロード/送信するCSV/ADIFとかは、これで十分やれる。

が、多言語対応が不十分で、QSOしたお相手のQTHやOp名に日本語が入っているとアウト。
(ちゃんとUnicodeで扱えば、あまり手をかけずに多言語対応できそうなのにね…)

なので、ちょっと強引にデータを引っこ抜く必要がある。

MacLoggerDXは、ログデータをSQLiteというその名の通り軽いSQLデータベースのファイル形式で保存している。
ホームディレクトリの直下、MLDX_Logsというフォルダの配下にあるMacLoggerDX.sqlというファイルに、ログ情報が記録されている。

SQLiteデータ用のビューワーはあれこれあるので、それらからCSVファイルにエクスポートしてもよい。

そうしようとも思ったが、いろいろ難がある。

QSO DateがUnixtime整数で保存されていたりして、CSVで出てきてもJSTに変換するのがめんどくさ!
だったり、そもそもJA局とDX局でJST/UTCを切り替えなきゃ、とか。
どのアンテナでOnAirしたかとか、真面目にログに入力したりしてねえし、ルールベースで一気にやっちゃえないか。

などと思うと、ロジックを組みたくなってしまい。
Pythonスクリプトでまとめて料理することにした。

といっても、たいしたロジックではなくて、基本はDBを読んで、CSVで出力するだけ。

その途中に、UTC/JSTの判定と変換をしたり、と少しごちゃごちゃやっている。

適当に書いた雑なスクリプトで個人的な都合のロジックも入っているが、どこかの誰かの足しになるかも知れないから、貼るだけ貼っておこう。

次回、TeXでのデータ面位置合わせ印刷編に続く。

import sqlite3
import datetime
import pytz
from dateutil import tz
import csv

#MacLoggerDX SQLite データベースファイル位置
SQLITE_FILE = "/Users/nob/Documents/MLDX_Logs/MacLoggerDX.sql"

#MacLoggerDX QSLite ログテーブルの列番号
SERIAL = 0
MY_RIG = 3
CALLSIGN = 4
FIRST_NAME = 5
LAST_NAME = 6
QTH_STREET= 7
QTH_CITY= 8
QTH_COUNTY= 9
QTH_STATE= 10
QTH_COUNTRY= 11
DXCC_COUNTRY=14
QSO_MODE=22
MY_RST=25
HIS_RST=26
QSL_VIA=27
QSL_SENT=28
COMMENTS=32
TIME_START=34
TIME_END=35
FREQ=38
POWER=42

#TimeZone変換用
tzJST = tz.gettz("Asia/Tokyo")
tzUTC = tz.gettz("UTC")

#SQLite DBファイルオープン (念のためReadOnlyで)
db_conn = sqlite3.connect(f'file:{SQLITE_FILE}?mode=ro',uri=True)

db_cursor = db_conn.cursor()

def isJACallsign(callsign): #JAコールサインかどうかの判定(雑)
    prefixJA = {"JA","JE","JF","JG","JH","JI","JJ","JK","JL","JM","JN","JN","JO","JP","JQ","JR","JS","7K","7L","7M","7N","7J","JD","7J","8J"}

    if isinstance(callsign,str):
        for prefixChk in prefixJA:
            if callsign.startswith(prefixChk):
                return True
    
        return False
    else:
        print("callsign is not str",type(callsign))
        return False

try:
    db_cursor.execute("SELECT * from qso_table_v007 order by qso_start asc")
    lines = db_cursor.fetchall()

    dtNow = datetime.datetime.now()
    csvFileName = "MLDX_Log_" + dtNow.strftime("%Y%m%d%H%M%S") +".csv"

    csvHeader = ["S#","QSL#","CALLSIGN","QSL_VIA","QSL_PSE","QSL_TNX","YEAR","MONTH","DAY","YMD","TIME","RST-MY","RST-HIS","FREQ","MODE","RIG","POWER","ANT","REMARKS1","REMARKS2","COMMENT","FIRST_NAME","LAST_NAME",
                 "COUNTRY","CITY","STREET","COUNTY","QSL_SENT"]

    with open(csvFileName,"w") as csvFile:
        writer = csv.writer(csvFile)
        writer.writerow(csvHeader)

        for line in lines:

            timeStr = line[TIME_START]
            qsoTimeStamp = int(timeStr)
            qsoTime = qsoTimeUtc = datetime.datetime.fromtimestamp(qsoTimeStamp,tzUTC)
            qsoTimeZone = "UTC"

            if True == isJACallsign(line[CALLSIGN]):
                #JA局だったらJSTに変換する
                qsoTime = qsoTimeUtc.astimezone(tzJST)#.replace(tzinfo=None)
                qsoTimeZone ="JST"
            
            qsoTimeStr = "{:02}".format(int(qsoTime.hour)) + ":" + "{:02}".format(int(qsoTime.minute)) + qsoTimeZone

            # リグ・出力・アンテナ情報の調整
            freq = float(line[FREQ])
            power = float(line[POWER])
            rigStr = line[MY_RIG]
            antStr =""

            #FT-991AMを使っているがCAT的にはFT-991なので変換
            #移動とかで他のリグを使っているときはそのまま
            if rigStr == "Yaesu FT-991": 
                rigStr = "Yaesu FT-991AM"

            if power>0:
                powerStr = "{:d}".format(int(power))
            else:
                powerStr =""

            freqStr = "{:.4f}".format(freq)

            if(freq>1200): #1200MHz帯だったら3桁に(印刷欄の都合上)
                freqStr = "{:.3f}".format(freq)
                powerStr = "1"
                rigStr ="Alinco DJ-G7" #G7しか持っていないからリグ情報編集忘れを救っておく
                antStr = "Whip"

            elif freq<440.0 and freq > 430.0:
                if(power>10):
                    antStr ="Yagi 5-ele 21m High"
                else:
                    antStr = "Whip"

            elif freq<146.0 and freq > 144.0:
                if(power>10):
                    antStr ="Yagi 3-ele 21m High"
                else:
                    antStr = "Whip"
            
            elif freq<54.0 and freq > 50.0:
                antStr = "Whip"

            else:
                if(power>10):
                    antStr = "MLA"
                else: #HFの移動は大体ロングワイヤー
                    antStr = "Long Wire"
                    
            modeStr = line[QSO_MODE]

            if "USB" in modeStr:
                modeStr = "USB"
            elif "LSB" in modeStr:
                modeStr = "LSB"

            # for JARL Contest Log sheet
            YMD = "{:04d}-{:02d}-{:02d}".format(qsoTime.year,qsoTime.month,qsoTime.day)

            #CSV出力
            csvRow = [line[SERIAL],"",line[CALLSIGN],line[QSL_VIA],"*","",
                        qsoTime.year,qsoTime.month,qsoTime.day,YMD,qsoTimeStr,
                        line[MY_RST],line[HIS_RST],
                        freqStr,modeStr,rigStr,powerStr,antStr,
                        "","",line[COMMENTS],
                        line[FIRST_NAME],line[LAST_NAME],
                        line[DXCC_COUNTRY],line[QTH_CITY],line[QTH_STREET],line[QTH_COUNTY],
                        line[QSL_SENT]]
            writer.writerow(csvRow)
        
finally:
    db_cursor.close()
    db_conn.close()





モバイルバージョンを終了