弊局JG6JAVは、通常運用記録とコンテスト運用もExcelを使っている。特に無線機の周波数とモードを自動的に記録することは重要だ。今までいろいろな手段でそれを行っていた。けれども時々問題発生していたのでもっと確実且つリアルタイムにその情報を取得する手段を模索していた。そこで最近頼りにしているChat-GPTに問い合わせたらlocalhost webサーバを使うという手法を提案された。そのwebサーバにExcel VBAからアクセスされたらその時点の最新情報を取得することができる。ということでさっそく組み込んでみたらストレスなく動作してくれている。IC7851RC2の全ソースコードは長いので今回取り組んだ部分のソースを何かの参考になればよいと思い公開する。
C# IC7851RC.exe IC-7851制御情報交換部
// Rig Info Data Class
public static class RigInfoData
{
public static int RunNr { get; set; }
public static bool Run { get; set; }
public static decimal VFO_Freq { get; set; }
public static string LogMode { get; set; }
public static int RFPower { get; set; }
public static string Callsign { get; set; }
public static string GetString()
{
return $"{RunNr};{Run};{VFO_Freq};{LogMode};{RFPower};{Callsign}";
}
}
//
// Start_RigServer のRigInfoを更新する。
private void Send_RigInfo(int nr, bool run, string callsign = "")
{
RigInfoData.RunNr = nr;
RigInfoData.Run = run;
RigInfoData.Callsign = callsign;
}
//
// 変更後: 戻り値を Task にし、async void は避ける
bool _isRigServerRunning = false;
CancellationTokenSource _cts = new ();
// Excel用 無線機情報サーバー アプリケーション起動時に起動
private async Task Start_RigServer()
{
// サーバーが既に実行中の場合は起動しない
if (_isRigServerRunning) return;
_cts = new CancellationTokenSource();
var token = _cts.Token;
_ = Task.Run(() => RunServerAsync(token));
}
//
private async Task RunServerAsync(CancellationToken token)
{
var listener = new HttpListener();
listener.Prefixes.Add("http://localhost:8080/");
listener.Start();
try
{
while (!token.IsCancellationRequested) // 停止指示がない間ループを継続
{
var contextTask = listener.GetContextAsync();
await Task.WhenAny(contextTask, Task.Delay(-1, token));
if (token.IsCancellationRequested)
{
break;
}
var ctx = contextTask.Result;
if (ctx.Request.RawUrl == "/riginfo") // Excel へ Rig Info 送信
{
RigInfoData.VFO_Freq = VFO_Freq.Value; // 周波数
RigInfoData.LogMode = LogMode; // ログ用のモード名
RigInfoData.RFPower = RI.RFP; // 出力
var RigInfo = RigInfoData.GetString();
byte[] buf = Encoding.UTF8.GetBytes(RigInfo);
ctx.Response.StatusCode = 200;
ctx.Response.ContentType = "text/plain; charset=utf-8";
ctx.Response.ContentEncoding = Encoding.UTF8;
// キャッシュを無効化
ctx.Response.Headers["Cache-Control"] =
"no-store, no-cache, must-revalidate, max-age=0";
ctx.Response.Headers["Pragma"] = "no-cache";
ctx.Response.Headers["Expires"] = "-1";
// 本文書き込み
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
ctx.Response.OutputStream.Flush();
ctx.Response.OutputStream.Close();
ctx.Response.Close();
this.BeginInvoke(() => StatusLabel.Text =
$"◎Rig Server Sent {RigInfo}");
}
if (ctx.Request.RawUrl == "/cw") // Excel からメッセージ受信
{
await HandleCwFromExcelAsync(ctx);
}
}
}
catch (HttpListenerException)
{
StatusLabel.Text = "Start_RigServer Http Listener Exception error";
}
catch (Exception ex)
{
StatusLabel.Text = $"Start_RigServer : Error {ex.Message}";
}
finally
{
if (listener.IsListening)
{
listener.Stop();
}
listener.Close();
_isRigServerRunning = false;
_cts.Dispose();
}
}
//
// Excel から送られたCWメッセージ送信と無線機の設定変更、内部変数変更。
private async Task HandleCwFromExcelAsync(HttpListenerContext ctx)
{
string msg;
using (var sr = new StreamReader(ctx.Request.InputStream, Encoding.UTF8))
{
msg = await sr.ReadToEndAsync();
}
// UIスレッドで SendCW 実行
BeginInvoke(new Action(() =>
{
var data = msg.Split(';');
switch (data[0])
{
case "#Set":
Change_RigSettings(data);
break;
case "#OnKey":
OnKeyControl(data[1]);
break;
case "#RunStatus":
Set_RunStatus(data[1] == "%Y", false);
break;
default:
SendCW(msg); // CW電文送出
break;
}
}));
byte[] res = Encoding.UTF8.GetBytes("OK");
ctx.Response.ContentType = "text/plain";
ctx.Response.ContentLength64 = res.Length;
await ctx.Response.OutputStream.WriteAsync(res, 0, res.Length);
ctx.Response.Close();
//
void Change_RigSettings(string[] data)
{
for (int i = 1; i < 4; i++)
{
switch (i)
{
case 1: // RF Power
Set_RigPower(int.Parse(data[i]));
break;
case 2: // Scope Scroll BSN
switch (data[2])
{
case "A":
Set_BSN(1, (3, 2), 2, 4);
break;
case "B":
Set_BSN(1, (3, 2), 2, 2);
break;
case "C":
Set_BSN(2, (4, 3), 3, 3);
break;
case "D":
Set_BSN(0, (3, 1), 1, 1);
break;
}
break;
case 3: // Contest CQ Message => TextBox F5msgに転記
if (data[3].Length > 0) { F5msg.Text = data[3]; }
break;
}
}
}
//
void OnKeyControl(string key)
{
modifierKeys = key.Substring(0, 1) switch
{
"%" => "alt",
"+" => "shit",
"^" => "ctrl",
_ => ""
};
if (modifierKeys.Length > 0)
{
key = key.Substring(1);
}
(var btn, var tb) = key switch
{
"{F1}" => (F1, F1msg),
"{F2}" => (F2, F2msg),
"{F3}" => (F3, F3msg),
"{F4}" => (F4, F4msg),
"{F5}" => (F5, F5msg),
"{F6}" => (F6, F6msg),
"{F7}" => (F7, F7msg),
"{F8}" => (F8, F8msg),
_ => (null, null)
};
if (btn != null)
{
TabControl1.SelectedIndex = 0;
F_button_click(btn, MouseEventArgs.Empty);
return;
}
if (key == "{TAB}" && modifierKeys == "^") { key = "{F10}"; } // Ctrl + Tab
switch (key)
{
case "{ESC}":
case "{F9}":
InterruptStop(); // CW Transmission interrupt stop
return;
case "{F10}":
SendCW(SendMsg.Text);
return;
}
}
}
//
// CancellationTokenSource をキャンセルし、RunServerAsync を中断させる
public void Stop_RigServer()
{
if (_isRigServerRunning && _cts != null)
{
_cts.Cancel();
}
}
この方法だとExcel VBAからアクセスするだけで無線機の周波数とモードの情報を取得できる。Excelからlocalhostへデータが書き込まれるとその内容により即応してCW送信、無線機の設定変更などが行われる。
Excel VBA jg6jav_log.xlsm 情報取得部分
' C# 起動中の RigServerにアクセス
Function HttpGetUTF8(url As String) As String
Dim xml As Object
Set xml = CreateObject("MSXML2.XMLHTTP")
xml.Open "GET", url, False
xml.send
Dim stream As Object
Set stream = CreateObject("ADODB.Stream")
stream.Type = 1 'binary
stream.Open
stream.Write xml.responseBody
stream.Position = 0
stream.Type = 2 'text
stream.Charset = "UTF-8"
HttpGetUTF8 = stream.ReadText
stream.Close
End Function
'
' RigServer で取得したデータを必要データに転記
Sub GetRigInfo(Optional Ret As Boolean = False)
Dim txt As String
txt = HttpGetUTF8("http://localhost:8080/riginfo")
Dim Rcv As Variant
Rcv = Split(txt, ";")
Dim i As Integer
Dim RigInfo As Range
Set RigInfo = Sheets("Settings").Range("RigInfo")
With RigInfo.Cells(1, 1)
For i = 0 To 5
.Offset(i, 0).Value = Rcv(i)
Next
End With
Dim Nr As Integer
Dim sta As Boolean
Dim Callsign As String
With RigInfo
Nr = .Cells(1, 1).Value '// マクロ選択番号
sta = .Cells(2, 1).Value '// Bloolean
rigFreq = .Cells(3, 1).Value '// Public 周波数情報
rigMode = .Cells(4, 1).Value '// Public モード
rigPower = .Cells(5, 1).Value '// Public 出力
Callsign = .Cells(6, 1).Value '// メモリーのコールサイン
End With
rigBand = Band(rigFreq)
If Ret Then Exit Sub
Select Case Nr
Case 1
Call RcvRigInfo(Callsign)
Case 2
Call StatusCheck(sta)
End Select
End Sub
IC7851RC2.exeからExcel VBA マクロ起動
// Run status 設定
private void Set_RunStatus(bool sw, bool post = true)
{
Running = sw;
RunFreq = Running ? VFO_Freq.Value : 0M;
if (post) { RunExcelMacro(1, Running); }
}
//
// Excel マクロ実行
void RunExcelMacro(int RunNr, bool args1 = false, string args2 = "")
{
Excel.Application xl =
(Excel.Application)Marshal.GetActiveObject("Excel.Application");
if (xl == null) return;
var macroName = RunNr switch
{
1 => "StatusCheck",
2 => "RcvRigInfo",
_ => ""
};
if (macroName.Length < 1) { return; }
try
{
xl.Run(macroName, RunNr == 1 ? args1 : args2);
}
catch (Exception ex)
{
StatusLabel.Text =
$"[RunExcelMacro] {macroName}({args1},{args2}) Error: {ex.Message}";
}
}
Excel VBA CW 送出用文字列渡し部分
Sub SendMsg(ByVal msg As String)
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "POST", "http://localhost:8080/cw", False
http.setRequestHeader "Content-Type", "text/plain; charset=utf-8"
http.send msg
End Sub
'
'---// ID 送信 //---
Sub SendMyID()
SendMsg("JG6JAV")
End Sub
'
'---// IC7851RCへSendkey //---
Sub IC7851(SKey As String)
SendMsg ("#OnKey;" & SKey)
End Sub
'
'---// IC7851RC 出力100Wに設定 //---
Private Sub RF100W()
Call SendMsg("#Set;100;D;")
End Sub
ログもMySQLなどに移行してすべてC#で構成するとよいのだろうけれどもExcelは何かと使い勝手が良いのでなかなか思い切れないでいる。だからといってアマチュア無線局の皆さんの間で多く使われているアプリケーションを使う気にはなれない。


コメント
フリーソフトってどうしてもサポートというか、いつまで使えるか、常に不安を感じてて。
自分で組んでみるかと思って、MS-DOS時代に使い倒した桐というDBソフトを購入。
QSL印刷用もコンテスト用もやっぱり国内産有名フリーソフトに全く敵う気がせず挫折。
フリーソフトを使わせていただいています。
ホントは、有料でいいのでどっかの会社組織がやってくれればいいなぁと思ってるんですが。
どこもやらないでしょうねぇ…。
フリーソフトは悪くはないのですが、自分で設計してないので操作を覚えるが面倒なのでログ関係は自前構築しています。なのでゴリゴリの自分用仕様です。