websocket.JSON を使った場合

JSON をやりとりする場合専用の方法がある

(JSON-RPC ライクな実装を書いてみた場合)

package main

import (
	"fmt"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type JSONRPCRequest struct {
	Method string      `json:"method"`
	Params []interface{} `json:"params"`
	Id     uint        `json:"id"`
}

type JSONRPCResponse struct {
	Id     uint        `json:"id"`
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

type JSONRPCEventResponse struct {
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

func main() {
	port := 51234

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		log.Printf("New websocket: %v", ws)
		var req JSONRPCRequest
		for {
			if err := websocket.JSON.Receive(ws, &req); err != nil {
				break
			}
			log.Printf("Request: %v", req)
			res := &JSONRPCResponse{Id: req.Id}

			switch req.Method {
			case "echo":
				res.Result = req.Params
			default:
				res.Error = "unkown method"
			}
			log.Printf("Response: %v", res)
			websocket.JSON.Send(ws, res)
		}
		log.Printf("Closed websocket: %v", ws)
	}))
	log.Printf("websocket server listen: %d", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
	if err != nil {
		panic(err)
	}
}

websocket.Message を使った場合

JSON だけやりとりするなら必要ないけどこのようにも書ける

  • websocket.Message.Receive(ws, &in)
  • websocket.Message.Send(ws, string(out))

をつかう。websocket.Message.Send の第2引数は byte[] だと JS 側では Blob を受けとれ、string だと JS 側では string を受けとれる。

バイナリを扱う場合、必然的にこちらをつかうことになる

同じことを冗長にやったコード

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type JSONRPCRequest struct {
	Method string      `json:"method"`
	Params interface{} `json:"params"`
	Id     uint        `json:"id"`
}

type JSONRPCResponse struct {
	Id     uint        `json:"id"`
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

type JSONRPCEventResponse struct {
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

func main() {
	port := 51234

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		log.Printf("New websocket: %v", ws)
		var in []byte
		for {
			if err := websocket.Message.Receive(ws, &in); err != nil {
				break
			}

			log.Printf("Request: %s", in)
			req := &JSONRPCRequest{}
			json.Unmarshal(in, req)
			res := &JSONRPCResponse{Id: req.Id}

			switch req.Method {
			case "echo":
				res.Result = req.Params
			default:
				res.Error = "unkown method"
			}
			out, err := json.Marshal(res)
			if err != nil {
				panic(err)
			}
			log.Printf("Response: %s", out)
			websocket.Message.Send(ws, string(out))
		}
		log.Printf("Closed websocket: %v", ws)
	}))
	log.Printf("websocket server listen: %d", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
	if err != nil {
		panic(err)
	}
}
  1. トップ
  2. tech
  3. golang で websocket
  1. トップ
  2. golang
  3. golang で websocket

KX3 はシリアル通信経由で直接 CW をエンコードして送信できる。つまりいわゆる専用の CW USB インターフェイスが必要がない。(RTTY も KX3 側でエンコードしてくれるので、これによって送信できる)

使うのは KY コマンドで

KY text;

の型式で送る。KY に続く空白を W にした場合、このコマンドで指定した文字列の送信が終わるまで、後続のコマンドの実行が保留される。(一旦キーの速度を変える場合に便利)

コマンドのリファレンスには「@ を送ると即時送信停止になるよ」というようなことが書いてあるが、これは K2 というリグの話で、KX3 ではあてはまらない。

KX3 では @ は常に @ のモールス符号が送出されるようになっていて、即時停止には RX コマンドを使う。RX コマンドは即時に送信を止めて受信モードに戻る (送信中符号の送信完了も待たない)

KX3 側のバッファはあまりないみたいだが、TB コマンドで今何バイトバッファに入っているかを知ることができる。ただ、9文字以上だと常に9になってしまうので、正確にはわからない。

この機能を使って PC キーイングを作る場合以下のようになりそう

  • TB コマンドを頻繁にポーリングして状態更新しつづける
  • 送信したい文字列はバッファにいれ、5文字程度までで分割して KY 実行
  • 残り2文字程度になったらバッファから残りを KY

KX3 側に保持されているバッファの内容を取得することはできず、残り送信文字数だけしかわからない。なので同期が正確にできないと厳しい。この機能は送信中にコマンド実行する必要があるので RFI が比較的でやすく信用できるかというと微妙な気がする。

とりあえず既に USB CW インターフェイスを作ったときの UI があって、WebSocket の互換サーバーさえ書ければ動かせるので、試してみる。

  1. トップ
  2. tech
  3. KX3 の KY コマンド
  1. トップ
  2. ham
  3. KX3 の KY コマンド

Mac はかっこいいんだけど、無線機とかのボタンとツマミがいっぱいあるかっこよさとは方向が違うんだよな。

スタイリッシュであることは翻っておもねり的ダサさも感じさせるから、そうじゃないボタンとツマミにまみれたナードデバイスというのは安定のかっこよさを持ってる。

15時ぐらいから悪寒がしたが MTG が連続で入っていたのでとりあえず出てから帰った。一晩で治るかなと期待したがそんなことなく1日休み

最近多いなと思ったので日記検索した

  1. トップ
  2. 体調不良
  3. 熱がでて一回休み

できたもの

できたものはこんな感じのです。

前提

KX3 にはオーディオ帯域までダウンコンバージョンされた信号が直接でる出力端子があり、一般的なステレオ音声入力を持つコンピュータを使ったソフトウェア解析により広帯域の無線信号を復調できる。

なのでこの信号を使って MacBook 上で FFT して広域の時系列データをウォーターフォール表示し、信号を高速で見つけられるようにしたい。

具体的には Windows においては OmniRig 及び HDSDR によって実現できることを Mac で行いたい。

問題

OmniRig と HDSDR は Mac 上に VM を立てて Windows を動かせば当然動くが、残念ながらパフォーマンスがあまりでない。特に CPU 使用率が非常に高くなってエコではない。

wine によるエミュレーションを行ってもある程度動かすことができるが、自分の環境では KX3 とのシリアル通信に難があった (書きこみはできるが読みこみができない) また、この方法も CPU リソースを大量に使う。

このため、Mac OS X 上でネイティブに動く似たようなソフトウェアが必要だと感じた。

最初の目標

  • FFT してウォーターフォール表示する
  • KX3 とシリアル接続し、周波数を相互に同期する

コンピュータ側で復調したりする機能は今回特に必要性を感じなかったので大変シンプルな要件

また、できれば KX3 側の局発を変えても、ウォーターフォール表示の履歴をスムーズに繋げたいと思った (既存の SDR ソフトだと必ず局発を中心にウォーターフォールが表示されてしまうため、時系列データがずれて表示されてしまう)

実装

今回実装言語として go を採用した。以下の理由がある

  • 最近流行っててかっこよさそうだから
  • ナイーブに書いてもそこそこ早いという噂があったから

音声入力まわりは portaudio、グラフィックス表示まわりは OpenGL を使って実装した。

FFT ウォーターフォール

入力の I/Q 信号は局発の位相を90度ずらしてコンバージョンした直交信号になっており、これを複素数として FFT すると、正の周波数と負の周波数を一発で解析できる。今まで FFT しても実数しか扱ったことがなく負の周波数部分は捨てたことしかなかったが初めてちゃんと使った。

基本、表示側はこれをうまいこと表示しているだけでおわってる。バッファは container/ring の ring.Ring でリングバッファとしていて、解析済みのビットマップを1行そのまま保持している。

シリアル通信

goserial というライブラリがあるので基本これを使ってやるだけだけど、いろいろとハマる。

  • FTDI のライブラリが不安定でよく kernel panic になる (強制再起動)
    • どうも read でブロックしているときに close しようとすると close も block し、そのときプロセスを無理矢理 kill させると Mac 全体が落ちる、ということがわかった。もしプロセスが block しているようなら、USB を物理的に抜くと read に EOF が返り、close も成功させることができる
  • KX3 は SET のコマンドにレスポンスを返さない
    • FA00007100000; とだけ送っても、それが成功したのかどうかはわからない。
    • FA00007100000;FA; として必ずレスポンスを返すようにして解決

go に慣れていなくて、make(chan T) し忘れたせいで一晩ハマったりもした (なぜか1度だけ chan 通信が動くという謎挙動になった)。

結果

当初の目的はひとまず果たすことができた。

  • MacBook Pro Retina, 13-inch, Late 2013 (2.4GHz Core i5 / 16GB メモリ / Intel Iris) において CPU は 30% 未満
    • もっと減らしたいけど、とりあえずまぁまぁいい感じ
    • GL の表示はかなりスムーズ
    • 処理が遅すぎると音声の入力バッファがあふれるので頑張る必要がある
  • バンド内でどれぐらい局が出ているか一見でわかりやすくなった
  • クリックしてすぐその信号を聞けるので捗る

40m (7MHz) 帯を見ながら実験してたけど、このバンドは終始賑やかというのが可視化されて面白かった。逆に、他のバンドはいつ見てもうちのロケーションでは殆ど聞こえないということもわかった。悲しい事実も可視化された。

あと気になっているのは、0Hz付近に常に高いスペクトルがでてしまうことだけど、よくわかってない。3000円ぐらいの安いサンプリングデバイスを使っているせいかもしれないけど、良さげなのは結構なお値段なのできつい。

Steinberg 2x2 USB 2.0 オーディオインターフェース UR22 - Steinberg(スタインバーグ)

Steinberg(スタインバーグ)

3.0 / 5.0

これが欲しいです。

  1. トップ
  2. tech
  3. KX3 用の Mac OS X バンドスコープ (Panadaptor) 実装を golang で
  1. トップ
  2. ham
  3. KX3 用の Mac OS X バンドスコープ (Panadaptor) 実装を golang で

"KXPD3 Keyer Paddle" は普通の KEY 端子とは別に、下部で4ピンで出ているのを使っている。マニュアルにも回路図にもピン配置が書いていないので調べた。(KXPD3 は持ってない)

ピン番号は回路図に倣った。KX3SchematicDiagramDec2012.pdf の9ページ

3V3 は何のためについているかわからないけど、330Ωがついているので10mAしか流れない。実測したけど9mAしか流れない。

GND はケースに導通しているのと共通

  1. トップ
  2. tech
  3. Elecraft KX3 Bottom Keyer Paddle Pin assign
  1. トップ
  2. ham
  3. Elecraft KX3 Bottom Keyer Paddle Pin assign