Skip to content

Commit 7bf872d

Browse files
committed
Online Logbooksに対応
QRZ.com HamQTH eQSL.cc HRDLog.net
1 parent 0a404fc commit 7bf872d

File tree

5 files changed

+326
-4
lines changed

5 files changed

+326
-4
lines changed

bridge.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ func startBridge() {
126126
b, _ := json.Marshal(payload)
127127
broadcast(string(b))
128128

129+
// Logbookへ非同期送信
130+
go submitLogbookAsync(adif)
131+
129132
}
130133
}
131134

config.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ type Config struct {
2828
RigPorts []RigPortConfig `json:"rig_ports"`
2929
RigBroadcastMode string `json:"rig_broadcast_mode"` // "single" or "all"
3030
SelectedRigIndex int `json:"selected_rig_index"` // "single"モード時のインデックス
31+
32+
// Logbook連携
33+
LogbookQRZAPIKey string `json:"logbook_qrz_apikey"`
34+
LogbookQRZEnabled bool `json:"logbook_qrz_enabled"`
35+
LogbookHamQTHCallsign string `json:"logbook_hamqth_callsign"`
36+
LogbookHamQTHUser string `json:"logbook_hamqth_user"`
37+
LogbookHamQTHPass string `json:"logbook_hamqth_pass"`
38+
LogbookHamQTHEnabled bool `json:"logbook_hamqth_enabled"`
39+
LogbookEQSLUser string `json:"logbook_eqsl_user"`
40+
LogbookEQSLPass string `json:"logbook_eqsl_pass"`
41+
LogbookEQSLEnabled bool `json:"logbook_eqsl_enabled"`
42+
LogbookHRDLogCallsign string `json:"logbook_hrdlog_callsign"`
43+
LogbookHRDLogCode string `json:"logbook_hrdlog_code"`
44+
LogbookHRDLogEnabled bool `json:"logbook_hrdlog_enabled"`
45+
LogbookClubLogEmail string `json:"logbook_clublog_email"`
46+
LogbookClubLogPass string `json:"logbook_clublog_pass"`
47+
LogbookClubLogCall string `json:"logbook_clublog_callsign"`
48+
LogbookClubLogAPI string `json:"logbook_clublog_api"`
49+
LogbookClubLogEnabled bool `json:"logbook_clublog_enabled"`
3150
}
3251

3352
var (

logbook.go

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"net/http"
6+
"net/url"
7+
"strings"
8+
"time"
9+
)
10+
11+
// submitLogbookAsync は非同期でADIFを各オンラインログサービスに送信します
12+
func submitLogbookAsync(adif string) {
13+
configLock.RLock()
14+
qrzEnabled := config.LogbookQRZEnabled
15+
qrzAPIKey := config.LogbookQRZAPIKey
16+
hamqthEnabled := config.LogbookHamQTHEnabled
17+
hamqthCallsign := config.LogbookHamQTHCallsign
18+
hamqthUser := config.LogbookHamQTHUser
19+
hamqthPass := config.LogbookHamQTHPass
20+
eqslEnabled := config.LogbookEQSLEnabled
21+
eqslUser := config.LogbookEQSLUser
22+
eqslPass := config.LogbookEQSLPass
23+
hrdlogEnabled := config.LogbookHRDLogEnabled
24+
hrdlogCall := config.LogbookHRDLogCallsign
25+
hrdlogCode := config.LogbookHRDLogCode
26+
// clublogEnabled := config.LogbookClubLogEnabled
27+
// clublogEmail := config.LogbookClubLogEmail
28+
// clublogPass := config.LogbookClubLogPass
29+
// clublogCall := config.LogbookClubLogCall
30+
// clublogAPI := config.LogbookClubLogAPI
31+
configLock.RUnlock()
32+
33+
// QRZ Logbook
34+
if qrzEnabled && qrzAPIKey != "" {
35+
go submitQRZLogbook(adif, qrzAPIKey)
36+
}
37+
38+
// HamQTH
39+
if hamqthEnabled && hamqthCallsign != "" && hamqthUser != "" && hamqthPass != "" {
40+
go submitHamQTH(adif, hamqthCallsign, hamqthUser, hamqthPass)
41+
}
42+
43+
// eQSL
44+
if eqslEnabled && eqslUser != "" && eqslPass != "" {
45+
go submitEQSL(adif, eqslUser, eqslPass)
46+
}
47+
48+
// HRDLog
49+
if hrdlogEnabled && hrdlogCall != "" && hrdlogCode != "" {
50+
go submitHRDLog(adif, hrdlogCall, hrdlogCode)
51+
}
52+
53+
// ClubLog (430ssb.net中継経由で実装予定 - ペンディング)
54+
// if clublogEnabled && clublogEmail != "" && clublogPass != "" && clublogCall != "" {
55+
// go submitClubLog(adif, clublogEmail, clublogPass, clublogCall, clublogAPI)
56+
// }
57+
}
58+
59+
// submitQRZLogbook はQRZ.com Logbookへ送信します
60+
func submitQRZLogbook(adif, apikey string) {
61+
defer func() {
62+
if r := recover(); r != nil {
63+
log.Println("[LOGBOOK] QRZ panic:", r)
64+
}
65+
}()
66+
67+
log.Println("[LOGBOOK] QRZ: sending...")
68+
log.Printf("[LOGBOOK] QRZ: KEY=%s", apikey)
69+
log.Printf("[LOGBOOK] QRZ: ADIF=%s", adif)
70+
71+
client := &http.Client{Timeout: 10 * time.Second}
72+
resp, err := client.PostForm("https://logbook.qrz.com/api", url.Values{
73+
"KEY": {apikey},
74+
"ACTION": {"INSERT"},
75+
"ADIF": {adif},
76+
})
77+
if err != nil {
78+
log.Println("[LOGBOOK] QRZ error:", err)
79+
return
80+
}
81+
defer resp.Body.Close()
82+
83+
// レスポンスボディを読み取る
84+
body := make([]byte, 1024)
85+
n, _ := resp.Body.Read(body)
86+
responseText := string(body[:n])
87+
88+
if resp.StatusCode == 200 {
89+
log.Printf("[LOGBOOK] QRZ: success - response: %s", responseText)
90+
} else {
91+
log.Printf("[LOGBOOK] QRZ: failed, status: %d, response: %s", resp.StatusCode, responseText)
92+
}
93+
}
94+
95+
// submitHamQTH はHamQTHへ送信します
96+
func submitHamQTH(adif, callsign, user, pass string) {
97+
defer func() {
98+
if r := recover(); r != nil {
99+
log.Println("[LOGBOOK] HamQTH panic:", r)
100+
}
101+
}()
102+
103+
log.Println("[LOGBOOK] HamQTH: sending...")
104+
105+
client := &http.Client{Timeout: 10 * time.Second}
106+
resp, err := client.PostForm("https://www.hamqth.com/qso_realtime.php", url.Values{
107+
"c": {callsign},
108+
"u": {user},
109+
"p": {pass},
110+
"adif": {adif},
111+
"prg": {"hamlab-bridge"},
112+
"cmd": {"insert"},
113+
})
114+
if err != nil {
115+
log.Println("[LOGBOOK] HamQTH error:", err)
116+
return
117+
}
118+
defer resp.Body.Close()
119+
120+
// レスポンスボディを読み取る
121+
body := make([]byte, 1024)
122+
n, _ := resp.Body.Read(body)
123+
responseText := string(body[:n])
124+
125+
if resp.StatusCode == 200 {
126+
log.Printf("[LOGBOOK] HamQTH: success (response: %s)", responseText)
127+
} else {
128+
log.Printf("[LOGBOOK] HamQTH: failed, status: %d, response: %s", resp.StatusCode, responseText)
129+
}
130+
}
131+
132+
// submitEQSL はeQSL.ccへ送信します
133+
func submitEQSL(adif, user, pass string) {
134+
defer func() {
135+
if r := recover(); r != nil {
136+
log.Println("[LOGBOOK] eQSL panic:", r)
137+
}
138+
}()
139+
140+
log.Println("[LOGBOOK] eQSL: sending...")
141+
142+
// GETリクエストのURLを構築
143+
params := url.Values{}
144+
params.Set("ADIFData", adif)
145+
params.Set("EQSL_USER", user)
146+
params.Set("EQSL_PSWD", pass)
147+
148+
reqURL := "https://www.eqsl.cc/qslcard/importADIF.cfm?" + params.Encode()
149+
150+
client := &http.Client{Timeout: 10 * time.Second}
151+
resp, err := client.Get(reqURL)
152+
if err != nil {
153+
log.Println("[LOGBOOK] eQSL error:", err)
154+
return
155+
}
156+
defer resp.Body.Close()
157+
158+
// レスポンスボディを読み取る
159+
body := make([]byte, 1024)
160+
n, _ := resp.Body.Read(body)
161+
responseText := string(body[:n])
162+
163+
if resp.StatusCode == 200 {
164+
log.Printf("[LOGBOOK] eQSL: success (response: %s)", responseText)
165+
} else {
166+
log.Printf("[LOGBOOK] eQSL: failed, status: %d, response: %s", resp.StatusCode, responseText)
167+
}
168+
}
169+
170+
// submitHRDLog はHRDLog.netへ送信します
171+
func submitHRDLog(adif, callsign, uploadCode string) {
172+
defer func() {
173+
if r := recover(); r != nil {
174+
log.Println("[LOGBOOK] HRDLog panic:", r)
175+
}
176+
}()
177+
178+
log.Println("[LOGBOOK] HRDLog: sending...")
179+
log.Printf("[LOGBOOK] HRDLog: Callsign=%s, UploadCode=%s", callsign, uploadCode)
180+
log.Printf("[LOGBOOK] HRDLog: ADIF=%s", adif)
181+
182+
client := &http.Client{Timeout: 10 * time.Second}
183+
resp, err := client.PostForm("https://robot.hrdlog.net/NewEntry.aspx", url.Values{
184+
"Callsign": {callsign},
185+
"UploadCode": {uploadCode},
186+
"ADIF": {adif},
187+
})
188+
if err != nil {
189+
log.Println("[LOGBOOK] HRDLog error:", err)
190+
return
191+
}
192+
defer resp.Body.Close()
193+
194+
// レスポンスボディを読み取る
195+
body := make([]byte, 1024)
196+
n, _ := resp.Body.Read(body)
197+
responseText := string(body[:n])
198+
199+
if resp.StatusCode == 200 {
200+
log.Printf("[LOGBOOK] HRDLog: success - response: %s", responseText)
201+
} else {
202+
log.Printf("[LOGBOOK] HRDLog: failed, status: %d, response: %s", resp.StatusCode, responseText)
203+
}
204+
}
205+
206+
// submitClubLog はClubLogへ送信します
207+
func submitClubLog(adif, email, password, callsign, apikey string) {
208+
defer func() {
209+
if r := recover(); r != nil {
210+
log.Println("[LOGBOOK] ClubLog panic:", r)
211+
}
212+
}()
213+
214+
log.Println("[LOGBOOK] ClubLog: sending...")
215+
216+
client := &http.Client{Timeout: 10 * time.Second}
217+
218+
values := url.Values{
219+
"email": {email},
220+
"password": {password},
221+
"callsign": {callsign},
222+
"adif": {adif},
223+
}
224+
225+
// APIキーが設定されている場合のみ追加
226+
if strings.TrimSpace(apikey) != "" {
227+
values.Set("api", apikey)
228+
}
229+
230+
resp, err := client.PostForm("https://secure.clublog.org/realtime.php", values)
231+
if err != nil {
232+
log.Println("[LOGBOOK] ClubLog error:", err)
233+
return
234+
}
235+
defer resp.Body.Close()
236+
237+
if resp.StatusCode == 200 {
238+
log.Println("[LOGBOOK] ClubLog: success")
239+
} else {
240+
log.Println("[LOGBOOK] ClubLog: failed, status:", resp.StatusCode)
241+
}
242+
}

rig.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ func handleCIVForPort(index int, b []byte) {
466466

467467
// parseCIVFrameForPort parses CI-V frame for a specific port
468468
func parseCIVFrameForPort(index int, f []byte) {
469-
log.Printf("CI-V PORT %d RAW: % X (len=%d)", index, f, len(f))
469+
//log.Printf("CI-V PORT %d RAW: % X (len=%d)", index, f, len(f))
470470
if len(f) < 7 {
471471
return
472472
}
@@ -1111,9 +1111,9 @@ func broadcastRigState() {
11111111
lastBroadcast.Proto = rigState.Proto
11121112

11131113
ev := map[string]interface{}{
1114-
"type": "rig",
1115-
"rig": rigState.Proto,
1116-
"port": rigState.Index,
1114+
"type": "rig",
1115+
"rig": rigState.Proto,
1116+
"port": rigState.Index,
11171117
}
11181118

11191119
if rigState.Freq > 0 {

webui.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,49 @@ button:hover {
289289
</div>
290290
</div>
291291
</div>
292+
<div class="checkbox-group">
293+
<div style="font-weight:600;margin-bottom:12px;color:#333;">📚 Logbook連携</div>
294+
<label class="checkbox-item">
295+
<input type="checkbox" name="logbook_qrz_enabled" {{if .Config.LogbookQRZEnabled}}checked{{end}}>
296+
<span>QRZ.com Logbook</span>
297+
</label>
298+
<div class="form-group" style="margin-left:28px;">
299+
<label for="logbook_qrz_apikey">API Key</label>
300+
<input type="password" id="logbook_qrz_apikey" name="logbook_qrz_apikey" value="{{.Config.LogbookQRZAPIKey}}">
301+
</div>
302+
<label class="checkbox-item">
303+
<input type="checkbox" name="logbook_hamqth_enabled" {{if .Config.LogbookHamQTHEnabled}}checked{{end}}>
304+
<span>HamQTH</span>
305+
</label>
306+
<div class="form-group" style="margin-left:28px;">
307+
<label for="logbook_hamqth_callsign">Callsign</label>
308+
<input type="text" id="logbook_hamqth_callsign" name="logbook_hamqth_callsign" value="{{.Config.LogbookHamQTHCallsign}}">
309+
<label for="logbook_hamqth_user">ユーザー名</label>
310+
<input type="text" id="logbook_hamqth_user" name="logbook_hamqth_user" value="{{.Config.LogbookHamQTHUser}}">
311+
<label for="logbook_hamqth_pass">パスワード</label>
312+
<input type="password" id="logbook_hamqth_pass" name="logbook_hamqth_pass" value="{{.Config.LogbookHamQTHPass}}">
313+
</div>
314+
<label class="checkbox-item">
315+
<input type="checkbox" name="logbook_eqsl_enabled" {{if .Config.LogbookEQSLEnabled}}checked{{end}}>
316+
<span>eQSL.cc</span>
317+
</label>
318+
<div class="form-group" style="margin-left:28px;">
319+
<label for="logbook_eqsl_user">ユーザー名</label>
320+
<input type="text" id="logbook_eqsl_user" name="logbook_eqsl_user" value="{{.Config.LogbookEQSLUser}}">
321+
<label for="logbook_eqsl_pass">パスワード</label>
322+
<input type="password" id="logbook_eqsl_pass" name="logbook_eqsl_pass" value="{{.Config.LogbookEQSLPass}}">
323+
</div>
324+
<label class="checkbox-item">
325+
<input type="checkbox" name="logbook_hrdlog_enabled" {{if .Config.LogbookHRDLogEnabled}}checked{{end}}>
326+
<span>HRDLog.net</span>
327+
</label>
328+
<div class="form-group" style="margin-left:28px;">
329+
<label for="logbook_hrdlog_callsign">Callsign</label>
330+
<input type="text" id="logbook_hrdlog_callsign" name="logbook_hrdlog_callsign" value="{{.Config.LogbookHRDLogCallsign}}">
331+
<label for="logbook_hrdlog_code">Upload Code</label>
332+
<input type="password" id="logbook_hrdlog_code" name="logbook_hrdlog_code" value="{{.Config.LogbookHRDLogCode}}">
333+
</div>
334+
</div>
292335
<button type="submit">保存</button>
293336
</form>
294337
<div class="version">HAMLAB Bridge v0.4.0</div>
@@ -365,6 +408,21 @@ func startWebUI() {
365408
}
366409
}
367410

411+
// Logbook連携設定
412+
config.LogbookQRZEnabled = r.FormValue("logbook_qrz_enabled") != ""
413+
config.LogbookQRZAPIKey = r.FormValue("logbook_qrz_apikey")
414+
config.LogbookHamQTHEnabled = r.FormValue("logbook_hamqth_enabled") != ""
415+
config.LogbookHamQTHCallsign = r.FormValue("logbook_hamqth_callsign")
416+
config.LogbookHamQTHUser = r.FormValue("logbook_hamqth_user")
417+
config.LogbookHamQTHPass = r.FormValue("logbook_hamqth_pass")
418+
config.LogbookEQSLEnabled = r.FormValue("logbook_eqsl_enabled") != ""
419+
config.LogbookEQSLUser = r.FormValue("logbook_eqsl_user")
420+
config.LogbookEQSLPass = r.FormValue("logbook_eqsl_pass")
421+
config.LogbookHRDLogEnabled = r.FormValue("logbook_hrdlog_enabled") != ""
422+
config.LogbookHRDLogCallsign = r.FormValue("logbook_hrdlog_callsign")
423+
config.LogbookHRDLogCode = r.FormValue("logbook_hrdlog_code")
424+
// ClubLogは430ssb.net中継経由で実装予定(ペンディング)
425+
368426
// リグ設定の変更をチェック
369427
rigSettingsChanged := false
370428
if oldUseRig != config.UseRig || oldUsePTY != config.UsePTY {

0 commit comments

Comments
 (0)