はじめに
ヤマハのルーターに対して、TeraTermマクロ(ttl)で設定を流し込む運用をしている方は多いと思います。sendln でコマンドを送って wait でプロンプトを待つ、というだけの単純な仕組みなので、一度組んでしまえば安定して動きます。
ただ、送りたいコマンドの中にダブルクォートが含まれていると、話が変わります。
今回、USB接続をトリガーにATコマンドを実行するスケジュールコマンドをマクロで投入したところ、エラーが一切出ないのに、コマンドが登録されないという状態になりました。マクロは最後まで完走し、save も実行され、TeraTermの画面上は何事もなく流れていきます。それでも設定が入っていない。
原因は引用符の入れ子でした。解決してみれば単純な話ですが、「エラーが出ない」という一点で切り分けに時間を取られたので、同じところで止まる人向けに記録を残しておきます。
環境
- ヤマハ FWX120
- USB型LTEドングル(ネクス UX302NC / UX302NC-R)
- TeraTerm マクロ(ttl)でシリアル・telnet経由の設定投入
- 複数台に同一設定を展開する前提
やりたかったこと
3G停波に伴い、USBドングルをLTEで接続させる必要が出てきました。
FWX120側でやることは大きく2つです。
- ファームウェアを、対象ドングルのLTE接続に対応したリビジョンに上げる
- USBが認識されたタイミングでドングルにATコマンドを渡し、LTE側を掴ませる
2つ目はスケジュールコマンドで実現できます。ヤマハのスケジュールコマンドは時刻起動だけでなく、USBの接続・切断をトリガーにできるので、ドングルを挿したタイミングで任意のコマンドを走らせられます。
書式は環境やリビジョンによって変わるので、正確なところはコマンドリファレンスを参照してください。ここでは一般形として、次のような1行を投入したかったものとして進めます。
schedule at 1 <USB接続をトリガーにする指定> <実行したいコマンド>
そして、この「実行したいコマンド」の中にATコマンドが入ります。ATコマンドは、パラメータを渡す形式だとダブルクォートを含みます。
AT+CGDCONT=1,"IP","your-apn"
つまり、コマンドの引数として、ダブルクォートを含む文字列を渡すという形になります。ここが今回の詰まりどころでした。
結論
先に結論を書きます。
- ダブルクォートを含むコマンドを
sendlnに1行で書くと、意図した文字列がそのまま届かないことがある - ルーター側はエラーを返さず、単に登録しない挙動になる場合がある(だから気づけない)
- ダブルクォートを変数に逃がし、
strconcatで組み立ててから送ると通る - マクロの最後に
show configで登録状況を確認し、ログに残す。これがないと失敗に気づけない
最初に書いたマクロ
最初は、投入したい行をそのまま sendln に書いていました。
; うまくいかなかった形(イメージ)
connect '...'
sendln 'administrator'
wait 'Password:'
sendln PASSWORD
wait '#'
sendln 'schedule at 1 <USB接続トリガー> <実行コマンド> AT+CGDCONT=1,"IP","your-apn"'
wait '#'
sendln 'save'
wait '#'
手元でコマンドを直接打ち込むぶんには、この行はそのまま通ります。だからマクロに移しても通るものだと思っていました。
症状:エラーが出ない、けれど入っていない
実際に流すと、こうなります。
- マクロは最後まで完走する
- ルーターからエラーメッセージは返ってこない
- プロンプトも正常に戻る
-
saveも実行されている - それなのに、スケジュールコマンドが登録されていない
保存漏れを疑いましたが、save はマクロに書いてあり、実行もされています。文法エラーなら Error: が返るはずですが、それもない。受け付けられたように見えて、何も起きていないという状態です。
この「エラーが出ない」が一番やっかいでした。エラーが出れば文言で検索できますが、出ないので手がかりがありません。
気づいたきっかけ:投入後に show config をログに残す
原因の特定より先に、そもそも失敗に気づけたきっかけの話を書いておきます。
複数台に展開する前提だったので、マクロの最後に投入結果を確認する処理を入れていました。設定を流したあとに show config を取り、対象の行が入っているかをログに残す形です。
; 投入後の確認
logopen 'C:\log\result.log' 0 1
sendln 'show config | grep schedule'
wait '#'
logclose
このログを見て、入っているはずの行がないと気づきました。
手作業で1台ずつ流していたら、画面が正常に流れているので「終わった」と判断してそのまま次に行っていたと思います。台数が増えてから発覚していたら、切り分けはもっと面倒になっていました。
投入して終わりではなく、投入後に確認して結果をログに残す。この形にしておいたことが結果的に効きました。
今回の環境では show config | grep がそのまま使えました。リビジョンによっては使えないこともあるので、その場合は show config を全部ログに落として、あとから該当行を探す形でも用は足ります。
原因:引用符が入れ子になっている
改めて、投入しようとしていた1行の構造を見ます。
sendln 'schedule at 1 ... AT+CGDCONT=1,"IP","your-apn"'
└─ マクロの文字列リテラル ────────────────────┘
└─ ルーターがコマンドの引数として解釈する範囲 ─┘
└─ ATコマンド自身の引用符 ─┘
引用符を解釈する層が3つ重なっています。
- TeraTermマクロが、
sendlnに渡す文字列リテラルとして解釈する層 - ルーターが、スケジュールコマンドの引数として解釈する層
- ATコマンド自身がパラメータを括るためにダブルクォートを使う層
外側の層が引用符を1つ外した時点で、内側の対応が崩れます。結果として、ルーターに届いた時点で引用符の対応が取れていない行になり、ルーターはそれを有効なスケジュール登録として扱わない、という挙動になりました。
やっかいなのは、構文としては受理されるところまで進んでしまう点です。完全に壊れていればエラーになりますが、中途半端に成立してしまうと、そのまま何も起きずに終わります。
手打ちだと通るのは、層が1つ少ないからです。手で打つときはマクロの文字列リテラルの層がないので、同じ文字列でも結果が変わります。「手打ちでは通るのにマクロだと通らない」ときは、まずこの差を疑うのが早いです。
対応:ダブルクォートを変数に逃がして組み立てる
エスケープの書き方を探すより、リテラルの中にダブルクォートを直接書かない方向に倒しました。
やり方はシンプルで、ダブルクォート単体を変数に持たせ、引用符のところで文字列を区切って strconcat で連結していきます。区切る位置を引用符に合わせておくと、リテラルの中に " が一度も出てこない状態になります。
; ダブルクォートを変数として持つ
QT = '"'
; 引用符の位置で区切って組み立てる
ATCMD = 'AT+CGDCONT=1,'
strconcat ATCMD QT
strconcat ATCMD 'IP'
strconcat ATCMD QT
strconcat ATCMD ','
strconcat ATCMD QT
strconcat ATCMD 'your-apn'
strconcat ATCMD QT
; スケジュールコマンドの行を組み立てる
CMD = 'schedule at 1 <USB接続トリガー> <実行コマンド> '
strconcat CMD ATCMD
; 送信
sendln CMD
wait '#'
区切りの粒度は好みで構いません。引用符をまたがないことだけ守れば、連結の回数を減らしてもう少しまとめて書いても同じです。
冗長に見えますが、この形にする利点があります。
- リテラルの中にダブルクォートが登場しないので、マクロ側の解釈で壊れる余地がなくなる
- 組み立てた文字列を送信前にログへ落とせるので、「何を送ったか」が確認できる
- APN名などの可変部分を変数として外に出しやすく、複数台への展開時に差し替えやすい
3つ目は特に効きます。1行に押し込んだ形だと、環境ごとの差分を反映するのに行そのものを書き換えることになりますが、変数に切り出しておけばマクロ冒頭のパラメータ部分だけを変えれば済みます。
送信前の確認を入れるなら、こういう形になります。
; 送信する内容をログに残してから送る
logopen 'C:\log\sent.log' 0 1
messagebox CMD 'send string' ; 検証時だけ有効にする
sendln CMD
wait '#'
messagebox は毎回止まるので、検証が終わったらコメントアウトするか、ログ出力に置き換えてください。
なお、ダブルクォートは文字コードで書く方法もあります。
sendln 'AT+CGDCONT=1,' #34 'IP' #34 ',' #34 'your-apn' #34
短く書けますが、後から読んだときに何をしているか分かりにくいので、複数人で触る運用なら変数に名前を付けておく方が無難だと思います。
結果
- スケジュールコマンドが正しく登録されるようになった
- USB認識をトリガーにATコマンドが実行され、LTE側で接続するようになった
- 投入後に
show configを取ってログに残す形にしたので、失敗した場合はログを見れば分かる - APN名などの可変部分を変数に切り出したことで、環境ごとの差し替えが楽になった
残課題・気をつけていること
エラーが出ない失敗は、確認を仕込まないと検知できない
今回の一番の教訓はここです。設定投入のマクロは「流して終わり」になりがちですが、流したあとに show config で確認してログに残す処理を入れておかないと、失敗に気づけません。エラーが出る失敗より、出ない失敗の方が危険です。
リビジョンによって書式が変わる
スケジュールコマンドの書式やオプションはリビジョンによって差があります。ここに書いた形をそのまま使うのではなく、必ず自分の環境のコマンドリファレンスで確認してください。
マクロにパスワードを平文で書かない
サンプルでは PASSWORD という変数で済ませていますが、これをマクロファイルに直接書くと、ファイルにパスワードが残ります。TeraTermマクロには passwordbox で都度入力させる方法と、getpassword で暗号化ファイルから読み出す方法があるので、どちらかを使ってください。設定投入用のマクロは端末に置きっぱなしになりやすいので、ここは最初に決めておいた方がいいです。
; 平文で書かない
getpassword 'pass.dat' 'router' PASSWORD
この話はヤマハ機に限らない
引用符が層をまたいで入れ子になる問題は、TeraTermマクロでCLI機器を叩くとき全般で起きます。送りたいコマンドの中に引用符が含まれるなら、まず変数に逃がす形を検討するのが安全です。
参考
- ヤマハ ルーター コマンドリファレンス(スケジュールコマンドの項)
- TeraTerm マクロ ヘルプ(
strconcat/getpassword/passwordboxの項)