SORACOM を使って学ぶ AT コマンド - 3. 実践編
<宣伝>
この記事の内容をまとめてさらにアップデートした内容で「SORACOM で学ぶ AT コマンド入門」という本(電子書籍)にしました。
</宣伝>
準備編では ATI
コマンドを実行してみました。
モデムを使ってダイヤルアップ接続をしてデータ通信を行うことができるようになるには、いくつかの AT コマンドを組み合わせて実行していく必要があります。
とはいっても、どこから手を付けたらいいかわからないので、まずは SORACOM が公開している、SORACOM Air でインターネットに接続するためのスクリプトを読み解いてみましょう。
SORACOM が実施しているハンズオンセミナーの資料が GitHub にて公開されています。 https://github.com/soracom/handson/blob/master/setup/setup.md#3-0
こちらの「接続スクリプトのダウンロード」というところからリンクされているスクリプトを見てみましょう。
途中に
Init1 = ATZ Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 Init3 = AT+CGDCONT=1,"IP","$2"
というような部分が見つかると思います。
これは、wvdial
の設定ファイルの内容ですが、ダイヤルアップ接続時にはここに記載されている AT コマンドを一つずつ実行していっているようなイメージとなります。
以下、各コマンドの説明をしていきますが、皆様も是非実際に手を動かしてコマンドを実行してみてください。
ATZ
コマンド
では、まず最初の ATZ
コマンドは何のためのコマンドでしょうか。
ITU-T Rec. V.250 6.1.1 によると、モデムの設定をデフォルト状態に戻すためのもののようです。
つまり、モデムをいったんまっさらな状態にしているわけですね。
もしモデムを使って接続中(データ通信中)に ATZ
コマンドを実行するとその接続は切断されるようです。
ATQ
コマンド
つづいては ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
という行を見ていきます。
長いコマンドに見えますが、実はこれは半角スペースごとに別の AT コマンドが続いています。
つまり、ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
は
ATQ0 ATV1 ATE1 ATS0=0 AT&C1 AT&D2 AT+FCLASS=0
という複数のコマンドを連続で実行したのと同じことになります。
ちなみに、基本コマンドは空白で区切らずに ATQ0V1E1S0=0&C1&D2
という感じにつなげて実行することもできます。
まずは最初の ATQ0
を見ていきましょう。
これは ITU-T Rec. V.250 6.2.5 によると、モデムが返す結果コード(OK
とか ERROR
とか)を抑制するかしないかを指定するコマンドです。
ATQ0
は抑制しない(結果コードが表示される)ようにします。
抑制したい場合は ATQ1
を実行します。
もし工場出荷時のデフォルトで結果コードを抑制する設定になっているようなモデムがあったとすると、コマンドの実行が成功しても失敗しても結果コード(OK
や ERROR
など)が何も表示されなくなってしまって問題の切り分け等が難しくなってしまうので、ATQ0
で確実に結果コードが表示されるようにしているのだと思います。
ATV
コマンド
続いて V1
の部分です。
これは、ITU-T Rec. V.250 によると、モデムからのレスポンスのフォーマットを選択します。
ATV0
および ATV1
が実行可能です。
それぞれの意味は、コマンドの実行例を見てもらったほうが早いと思います。
まず ATV0
を実行してから ATI
を実行した場合は、以下のような結果になります。
Manufacturer: huawei Model: MS2131 Revision: 21.751.19.00.00 IMEI: 86610xxxxxxxxxx +GCAP: +CGSM,+DS,+ES 0
ATV1
を実行してから ATI
を実行すると、以下のようになります。
ATV1 OK ATI Manufacturer: huawei Model: MS2131 Revision: 21.751.19.00.00 IMEI: 866109028769760 +GCAP: +CGSM,+DS,+ES OK
ATV0
のときと ATV1
のときの違いは以下のような点です。
ATV0
のときはコマンドを入力した行に上書きされるような形で結果が表示される。ATV1
のときはコマンドを入力した次の行から結果が表示されるATV0
のときはATI
コマンドの最後の結果コードが数値(0
はOK
と同じ意味)。ATV1
のときはOK
と文字列で表示されるATV0
のときは結果の数値の前に空行が入らない。ATV1
のときは結果コードの前に1行空行がある。
通常は ATV1
を設定しておいたほうが(人間にとって)読みやすくて良いでしょう。
ATE
コマンド
つづいては E1
です。
これは ITU-T Rec. V.250 6.2.4 によると、モデムに送った AT コマンドの文字列をモデム側からエコーバックするかどうかを決めるコマンドです。
ATE0
ではエコーバックしない、ATE1
ではエコーバックする設定です。
screen
で AT コマンドを試してみる際にはエコーバックがないと辛いので ATE1
を設定しておいたほうが良いでしょう。
ATS0
コマンド
つづいては S0=0
です。
ITU-T Rec. V.250 6.3.8 によりますと、これは電話がかかってきた際に何回の呼び出し音の後に自動応答をするかを決めるコマンドです。
ATS0=0
は自動応答しない設定です。
AT&C
コマンド
続いては &C1
です。
ITU-T Rec. V.250 6.2.8 によると、"Circuit 109" の挙動を設定します。
"Circuit 109" とは、接続先からのキャリア信号を受信したこと(≒接続先に接続できたこと)を、DCE(≒モデム)が DTE(≒ユーザープログラム等)に伝えるための信号線の名前です。"Received Line Signal Detector" とか "DCD" と呼ばれたりもします。
AT&C0
では、接続できているかどうかに関わらず Circuit 109 が "ON" 状態になります。
AT&C1
では、接続状態に応じて Circuit 109 の状態が変化します。
通常は &C1
に設定しておきます。
AT&D
コマンド
続いては &D2
です。
ITU-T Rec. V.250 6.2.9 によりますと、"Circuit 108/2" が "ON" から "OFF" になったときのモデムの挙動を設定します。
"Circuit 108/2" とは、DTE(≒ユーザープログラム等)がデータ送受信の準備が整っているかどうかを DCE(≒モデム)に伝えるための信号線の名前です。"Data terminal ready" とか "DTR" と呼ばれたりもします。
つまり、"Circuit 108/2" が "ON" から "OFF" になったということは、ユーザープログラムが終了するなどしてデータ送受信できなくなったことを示し、その際にモデムがどのように振る舞えばよいかを設定することになります。
これには設定可能な値が 3 つあります。
AT&D0
の場合は、モデムは "Circuit 108/2" の変化を無視します。(つまりデータ送受信を継続しようとします)
AT&D1
の場合は、モデムは通話は継続したままコマンドモードに戻ります。
AT&D2
の場合は、モデムは通話を終了します。未送信のデータが残っている場合の挙動は +ETBM
で設定されたパラメータにしたがって処理されます(モデムが対応している場合)
意図せずプログラムが終了してしまった場合などに接続が継続されてしまうと思わぬ課金が発生したりしてしまうかもしれませんので、ここでは &D2
に設定しているのだと思われます。
AT+FCLASS
コマンド
続いては +FCLASS=0
です。
これは入門編でもご紹介したように +
で始まっているので拡張コマンドになります。
そして +F
なので FAX 関連のコマンドということになりそうです。
ITU-T Rec. V.250 の Supplement 1 (ITU-T Rec. V.250 をダウンロードしたのと同じページからダウンロードできます)を見ると、+FCLASS
は ITU-T Rec. T.31 の 8.2.1 などで規定されているようです。
ということで ITU-T Rec. T.31 をダウンロードして確認してみましょう。
これは "サービスクラス" というものを選択するコマンドのようです。
FAX の送受信は行わず、データ通信のみの場合は +FCLASS=0
を指定するようです。
なお、Huawei MS2131i-8 と L-05A は +FCLASS
コマンドをサポートしていませんでした。
(実行すると結果コードが ERROR
になります。)
+FCLASS
コマンドをサポートしているかどうかは +GCAP
コマンドを実行するとわかります。
AT+GCAP +GCAP: +CGSM,+DS,+ES OK
ここに +FCLASS
が含まれていないので、このモデムは +FCLASS
コマンドをサポートしていないようです。
AT+CGDCONT
コマンド
さて、長かった ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
の解説がやっと終わりました。
次は AT+CGDCONT=1,"IP","$2"
です。
$2
の部分は、これはシェルスクリプトの関数に渡されてくる引数で、実際には "soracom.io" という文字列になっています。
ですので、このコマンドは実行されるときには AT+CGDCONT=1,"IP","soracom.io"
というコマンドとして実行されます。
これも AT
の直後が +
なので拡張コマンドです。+C
なので携帯電話回線の制御のコマンドということがわかります。
ITU-T Rec. V250 Supplement 1 を見ると、+CGDCONT
は GSM07.60 という標準で定められているようです。
GSM 07.60 は一般公開はされていないようですが、3GPP TS 27.060 が GSM 07.60 と同じものなので 3GPP のほうを見ましょう。
27.060 の 12.1 には AT コマンドの概要が書かれていますが、詳細なリファレンスは 3GPP TS 27.007 に記述されている旨が記されています。
ということで今度は 3GPP TS 27.007 を参照しますと 10.1.1 に +CGDCONT
の定義がありました。
これは "PDP コンテキスト" と呼ばれるものを設定するコマンドです。"PDP" は Packet Data Protocol の略で、要はデータ通信のことです。PDP をするために必要な情報を設定します。
ここでは 1
, "IP"
, "soracom.io"
という3つのパラメータを設定しています。
最初の 1
は、コンテキストの ID で、1からいくつかの数が使えます。PDP コンテキストを保存しておくスロットの番号のようなものです。あとでこの設定を参照するためにこの 1
という値を使います。
"IP"
は PDP タイプというもので、 SORACOM の場合は今のところ "IP"
を固定で指定しておけば良いです。
"soracom.io"
は APN 名です。
ここまで実行したら、設定が完了しました。
ATD
コマンド
設定が完了したらダイヤルアップします。
ダイヤルアップするには ATD
コマンドを使用します。
ITU-T Rec. V.250 の 6.3.1 に ATD コマンドの基本的な定義があり、3GPP TS 27.007 の 6.2 には D コマンドが携帯電話回線で使われるときの追加仕様や変更点について少し書かれています。
SORACOM SIM でダイヤルアップするには ATD*99***1#
というコマンドを実行します。
これは *99***1#
という番号にダイヤルしているのですが、この番号の末尾の #
の一つ前にある 1
は、先ほどの +CGDCONT
で指定したコンテキスト ID の 1
です。+CGDCONT
で異なるコンテキスト ID で複数の設定を保存している場合、ここのダイヤル先番号を変えてどのコンテキストを使うかを選択します。
ATD*99***1#
を実行すると
CONNECT 21600000
のような応答が返ってくると思います。(モデムの機種によっては数字が表示されたりされなかったりするかもしれません)
この状態で、ダイヤルアップ接続をしてソラコムの認証サーバーとの通信が始まっています。
本当であればここでプロトコルに則ってユーザー名・パスワード(いずれも "sora")を送信したり、IP アドレス情報や DNS サーバー情報を受信したりしてセッション開始となるのですが、screen コマンドで手作業で AT コマンドを実行している今の状態ではそれらのプロトコルを処理することがさすがにできません。
Linux では wvdial コマンドがそのあたりをやってくれて、ppp0 インターフェースを作成してくれたりしてデータ通信ができるようになります。
自作のプログラム等であれば、これまで見てきたような AT コマンドを使って接続したあとにここで認証処理を実施すればデータ通信が行えるようになるはずです。
なお、この状態では screen の画面上で何かキーを打ったりしても反応が無くなります。抜け出すには screen ごと終了する必要があります。
機種依存コマンド
機種によっては、標準で定められていない独自拡張コマンドを使うことで、AT コマンドだけでデータ通信を行うことができるようになるものもあります。
たとえば SORACOM の販売している Quectel 社製の 3G 通信モジュール UC20-G や EC21-J には AT+Q
で始まる独自コマンドを使うことによって AT コマンドのみでデータ通信ができるようになっています。
(以前の記事でちらっと解説しています)
そのような機種依存のコマンドについては、メーカーが AT コマンドのリファレンスを提供していると思いますのでそれを参照するのが良いでしょう。
AT+C
系の興味深いコマンド
ここからはデータ通信ではないのですが、AT コマンドを使って面白いことができるということをご紹介していきたいと思います。
以下の情報は、機種によってはサポートしていなかったりしますが、使えると有益な情報が得られたりして非常に便利です。
AT+CGREG
これは PS (Packet Switched) ドメイン、すなわちデータ通信用ネットワークへの登録状況を取得するコマンドです。
SIM がモデムに入っている状態で AT+CGREG
を実行すると、デフォルトのままだとおそらく以下のような結果が得られるのではないかと思います。
AT+CGREG? +CGREG: 0,1 OK
という感じの結果が返ってきます。 これの何が面白いのかと思われるかもしれませんが、以下のようにモードを変更してから再度実行すると今度は何やら面白そうな値が取得できます。
AT+CGREG=2 OK AT+CGREG? +CGREG: 2,1,"00B0","04AF4173" OK
このレスポンスを解説しましょう。
まず、AT+CGREG=2
では、この後の AT+CREG?
コマンドのモードを変更しています。モードを 2
にすると、AT+CREG?
コマンドは GPRS ネットワークへの登録状況の他に、位置情報(基地局の ID やエリアの ID)を返すようになります。
上記で得られたのは "00B0"
がエリアコード lac: location area code
と "04AF4173"
が基地局 ID(cid: cell id) です。
この情報を、地図上の座標に変換してくれるサービスがあります。 Google や Firefox なども API を提供しているようですが、ここでは OpenCellID.org というサイトを使ってみましょう。
サインアップは簡単です。名前、メールアドレスを入力し、用途を選択して Submit したら、Token というパスワードのようなものが送られてきますのでそれを使ってサイトにログインします。
ログインしたら、以下のように MCC = 440, MNC = 10, LAC, CID を入力します。
MCC = 440 は日本を表す Mobile Country Code です。
MNC = 10 は日本の中で NTT ドコモを表す Mobile Network Code です。
SORACOM の SIM は NTT ドコモの電波を掴むのでこれらの値を設定します。
LAC と CID は先ほどの AT+CGREG?
コマンドで得られた16進数の値を10進数に変換したものです。
(10進数への変換は、Mac や Linux の場合は printf %d "0x04AF4173"
というように printf
コマンドを使うと簡単でしょう)
このときはソラコムの二子玉川オフィスで基地局情報を取得しましたが、オフィスの近くの場所が示されていることがわかると思います。
GPS を搭載していないデバイスでも、基地局の位置情報を使うことで、「だいたいどのあたりにいるか」という感じの大まかな位置情報が利用可能となります。
なお、デバイスや携帯電話会社によってはこの情報を取得できない場合もあるようです。
それから、SORACOM のグローバル SIM を使ってローミングしている場合にも、接続先の国やオペレーター(電話会社)によって取得できたりできなかったりすることもあるようですので、あくまでも「利用できたらラッキー」という位の気持ちで使っていただくと良いでしょう。
AT+CGREG
によく似たコマンドで AT+CREG
(C
の後ろに G
が入っていない)というのもありますが、こちらは CS (Circuit Switched) ドメイン、いわゆる音声通話や SMS 送受信用のネットワークへの登録状況を取得します。
SIM の種類がデータ通信専用のものだと PS ドメインにしか接続しないので、SORACOM の SIM の場合はとりあえず C
の後ろに G
が入っている方の、 AT+CGREG
を実行しておくと良いでしょう。SMS つきの SIM を使っている場合、 AT+CREG
コマンドでも登録状況が取得できるはずです。
逆に他社の SIM でデータ通信をサポートしていない(音声通話のみの)SIM を入れると AT+CREG
のみ結果が得られるはずです。
ちなみに、拡張コマンドの多くは AT+XXXX?
で現在の値の取得、AT+XXXX=Y
で値の設定、AT+XXXX=?
で設定可能な値のリストの取得、というような書式になっているものが多いようです。
AT+CSQ
コマンド
こちらは、電波の強度(Signal Quality)を取得するコマンドです。 実行してみると、以下のような結果が得られるはずです。
AT+CSQ +CSQ: 17,99 OK
17
と 99
の2つの数が見えます。
1つめの数は RSSI (Received Signal Strength Indicator) です。
RSSI の値の範囲は 0 〜 31 です。
RSSI の値が 0 の場合は -113 dBm かそれ以下、RSSI の値が 31 の場合は -51 dBm かそれ以上、RSSI の値が 1 から 30 の間の場合は、その値を n とすると、-113 + (n * 2)
[dBm] となります。今回の場合 17
なので、-79 dBm という事になります。
2つめの数は BER (Bit Error Rate) です。 BER の値が 0〜7 のときは 3GPP TS 45.008 の 8.2.4 にある表にしたがって導出されるエラーレートであることがわかります。 BER の値が 99 の場合は、ビットエラーが起きたかどうか不明な場合か検出できなかった場合です。
おわりに
入門編、準備編、そしてこの実践編と3記事に渡って AT コマンドをいろいろ見てきましたが、いかがでしたでしょうか。 少しでも AT コマンドへの理解が深まったとしたら幸いです。 ここまでの 3 つの記事の内容を理解していれば、大抵の AT コマンドは、ITU-T や 3GPP、そしてデバイス製造元の発行しているドキュメントを参照しつつ、理解したり使いこなしたりできるようになっているはずです。
今絶賛流行中の Wio LTE も、Quectel 社の EC21-J というモジュールを搭載していてそのモジュールに対して AT コマンドを実行したりできますので、ぜひ試してみてください。
本当は SMS の送受信とかも AT コマンドでできるのでそこまでご紹介したかったのですが、(そろそろ力尽きて来たので)それはまたの機会にとっておきたいと思います。
ちなみに SMS 関連の AT コマンドは 3GPP TS 27.005 に定義されていますので、興味のある読者はぜひ参照してみてください。
SORACOM を使って学ぶ AT コマンド - 2. 準備編
<宣伝>
この記事の内容をまとめてさらにアップデートした内容で「SORACOM で学ぶ AT コマンド入門」という本(電子書籍)にしました。
</宣伝>
前回の記事では、AT コマンドの概要を学びました。
今回は、AT コマンドを実際に実行したりしていろいろ試してみるために、準備を行っていきます。
以下のものを用意しましょう。
- USB タイプのモデム(Huawei MS2131i-8 もしくは Abit AK-020)
- SIM カード
モデム Huawei MS2131i-8 は SORACOM のユーザーコンソールでカバレッジタイプをグローバルに変更してからメニューの「発注」ページで購入します。
こちらの製品は価格が 50 米ドル、送料込で日本円に換算して約7,000〜8,000円程度です(いずれも2017年12月現在)
Abit AK-020 は同じく SORACOM のユーザーコンソールで購入できますが、こちらはカバレッジタイプを「日本」にしてから、メニューの「発注」ページで購入します。
もし SORACOM の SIM カードをまだ持っていない場合は、こちらの AK-020 に SIM カードとクーポン(従量課金のデータ通信量部分に適用可能)がセットになったスターターキットを購入されるとよいかもしれません。
3G USBドングル AK-020 SORACOMスターターキット
- メディア: エレクトロニクス
もし、他の USB 接続タイプのモデムをすでに持っていらっしゃる場合はそちらを使用しても大丈夫かもしれませんが、この記事のこの後のコマンドの実行などの際に文字列を読み替えていただいたりする必要が出てくるかもしれません。
モデムを入手したら、お手元のコンピューター用のデバイスドライバーをダウンロードしてインストールしてください。
Huawei MS2131i-8 の場合はこちらのブログ記事が参考になるかと思います。 https://blog.soracom.jp/blog/2017/11/08/huawei-ms2131i-8/
AK-020 の場合は取扱説明書がついてきますのでその通りにインストールを行います。(Mac の場合は Web サイトから最新版をダウンロードして使う必要があります)
また、コンピューターの USB ポートにモデムを挿して、モデムに付属のダイヤルアップ用アプリケーションなどで正常に接続でき、データ通信ができることも確認しておきましょう。
ここまで確認できたら、AT コマンドを実行できるようにしていきましょう。
macOS の場合
私は macOS 10.12 (Sierra) で動作確認を行いました。
まず、ターミナルを起動して以下のコマンドを実行してみてください。
Huawei MS2131i-8 を使っている場合:
ls /dev/tty.HUAWEIMobile-*
Abit AK-020 を使っている場合:
ls /dev/tty.USB*
注意: 上記以外のモデムを使用している場合や、OS のバージョンが異なる場合はデバイスファイル名が変わるかもしれません。/dev 以下でそれらしい名前のデバイスファイルを探してみてください。
(私が所有している L-05A というモデムの場合は /dev/tty.usbmodem*
という感じのデバイスファイル名でした。)
実行すると以下のような 3 つのデバイスファイルが見つかるはずです。
MS2131i-8 の場合:
/dev/tty.HUAWEIMobile-Diag /dev/tty.HUAWEIMobile-Modem /dev/tty.HUAWEIMobile-Pcui
AK-020 の場合:
/dev/tty.USB Application Port_ /dev/tty.USB Modem Port_ /dev/tty.Speech Port_
次に、以下のコマンドを実行してみてください。
MS2131i-8 の場合:
screen /dev/tty.HUAWEIMobile-Modem
AK-020 の場合:
screen /dev/tty.USB\ Modem\ Port_
これでモデムに接続できて、ターミナルの画面が真っ黒になった(プロンプトなどの表示が消えた)はずです。
(なお L-05A の場合は screen /dev/tty.usbmodem1411
で接続できました)
接続できたらAT
と入力し、Enter を押してみてください。
AT OK
というように OK
というレスポンスが返ってきたら成功です。
つぎに、ATI
と入力して Enter を押してみてください。
MS2131i-8 の場合は以下のようなレスポンスがありました。
ATI Manufacturer: huawei Model: MS2131 Revision: 21.751.19.00.00 IMEI: 86610xxxxxxxxxx +GCAP: +CGSM,+DS,+ES OK
(IMEI はハードウェアの固有の ID ですので念のためマスクしてあります。)
AK-020 の場合は以下のようなレスポンスがありました。
ATI MTK2 AK-020_V05_20160517 OK
もし反応がない場合は、デバイスファイル名を、さきほど ls
コマンドで見つけた他のものに変えて実行してみてください。
なお、screen コマンドを終了するには Ctrl-a
k
と入力します。(Ctrl-a
を押した後、Ctrl キーを離して k
を押します。なお、環境によってはコマンドを起動するためのキーの割り当てがデフォルトの Ctrl-a
から変更されている場合もあります。その場合はそちらを使ってください)
ちなみに、この ATI
は I
という AT コマンドを実行したことになります。
I
コマンドは ITU-T Rec. V.250 の 6.1.3 Request identification information に詳しく定義されていますが、モデムを識別する情報を表示するコマンドです。
メーカーや製品のモデル等によってどのような内容が表示されるかが変わります。
モデムによっては、ATI0
といったように、数値を続けて指定することができます。
Huawei MS2131i-8 も AK-020 もともに数値を指定してもレスポンスが変わりませんでしたが、メーカーや機種によっては指定した数値によってレスポンスの内容が変わる場合があるようです。
最後の OK
は AT コマンド共通の結果コードで、コマンドの実行が成功したことを示します。
何らかのエラーが起こった場合は ERROR
が返ります。
ここまでできたら準備完了です。
次の記事では、いよいよ実践的な AT コマンドを見ていきましょう。
Windows の場合
Linux の場合
SORACOM を使って学ぶ AT コマンド - 1.入門編
<宣伝>
この記事の内容をまとめてさらにアップデートした内容で「SORACOM で学ぶ AT コマンド入門」という本(電子書籍)にしました。
</宣伝>
この記事は SORACOM Advent Calendar 2017 4日目の記事です。
SORACOM の SIM を使っていると、「AT(エーティー)コマンド」というものを取り扱わないといけない場面にたまに出会いますよね。
私は出会います。
AT+CGDCONT=1,"IP","soracom.io"
とかそんな感じのやつです。
この「AT コマンド」が「モデムを制御したり情報を取得したりするためのもの」というのはなんとなく知っているけれど、基本的にはネット上で見つけたのをあまり意味もわからずコピペして使ってたり、コマンドの意味を調べようと思ってもなかなかまとまった情報が見当たらなかったり、そもそも体系的に勉強しようとしてもどこから手を付けたらいいかわからない、、、というような感じの人が多いのではないでしょうか。
私はそうでした。
日本語で書かれた良い入門記事が Web 上に見当たらなかったので、「ないものは作ってしまえ」メソッドで、ちょっと簡単にまとめてみたいと思います。
ツッコミ大歓迎です。
概要
AT コマンドの起源は 1981 年にヘイズ コミュニケーションズ (Hayes Communications) 社の固定電話回線用のモデムに導入された、電話をかけたり切ったりといった電話回線の制御やモデムの設定変更等に使われたコマンドに端を発するそうです。
コマンドの文字列が、皆さんご存知のように “AT” で始まるので日本語ではよく「AT コマンド」と言うと思いますが、英語では「ヘイズ コマンド セット (Hayes command set)」とも言うようです。(英語版の Wikipedia で「AT commands」と検索すると、https://en.wikipedia.org/wiki/Hayes_command_set に転送されます)
その後、モデムを作っていた各社が Hayes の真似をして独自拡張などを入れながら AT コマンドがモデムの制御のためのデファクトスタンダードとして広まっていったようです。
現在は ITU-T 勧告 (Recommendation) V.250(以下、ITU-T Rec. V.250)にて AT コマンドの標準が定められています。
ITU-T Rec. V.250 では、用語の定義、DTE と DCE の間の物理的な接続方法の取り決め、データレート、AT コマンドのシンタックス、多数の基本的な AT コマンドの仕様などが定められています。
ITU-T Rec. V.250 は ITU のサイトから誰でも無料でダウンロードして読むことができます。 https://www.itu.int/rec/T-REC-V.250-200307-I/en
なお、ITU-T Rec. V.250 では AT コマンドは「AT command set」と呼ばれています。そして AT は attention
の先頭の2文字である旨も示されています。
携帯電話回線のためのモデムを制御するための AT コマンドの仕様は、ITU-T Rec. V.250 をベースに、3GPP TS 27.007 および 27.005 で標準が定められています。
27.007 および 27.005 は 3GPP のサイトから誰でも無料でダウンロードして読むことができます。 https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1515 https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1514
ITU-T Rec. V.250 は PDF で配布されていますが、3GPP TS 27.007 および 27.005 は Microsoft Word 形式のファイルなので読むために対応ソフトが必要です。
この後、上記 3 つのドキュメントをちょくちょく参照することになると思いますのでダウンロードして手元で見られるようにしておくと良いかもしれません。
AT コマンドのシンタックス
ほとんどの AT コマンドは、AT
の 2 文字で始まる文字列です。
AT
以外に、直前のコマンドを繰り返す A/
というコマンドもありますが、それ以外は基本的に AT
で始まります。
AT コマンドの文字列を何らかの方法でモデムに送信すると、モデムはそのコマンド文字列を解釈して様々な動作を行います。 どうやってモデムにコマンド文字列を送るかについては、モデムにシリアルポートがある場合はそのシリアルポート経由で送信したり、Unix 系の OS では tty が割り当てられたりしますのでそこに送信する場合もあります。
AT コマンドは通常は大文字の AT
で始まる文字列ですが、小文字の at
も許されているようです。大文字小文字の混在(aT
もしくは At
)は ITU-T Rec. V.250 では明示的には許可されていないように見受けられましたが、混在した文字列を送信しても問題なく認識してくれるモデムが多いようです。
とはいえ、AT
と大文字で書くのが一般的かと思いますので、この記事でも大文字を使います。
AT
の次に、アルファベットもしくは記号が続きます。
たとえば ATD
というコマンドは電話をかけるコマンドです。
ATD12345678
という AT コマンドを実行すると、12345678
という番号に電話をかけます。
SORACOM SIM を使ってダイアルアップ接続する際には ATD*99***1#
というようなコマンドを実行しますが、これは *99***1#
という(ちょっと不思議な)番号に電話をかけているということになります。
電話番号として使える文字は、
0 1 2 3 4 5 6 7 8 9 * # + A B C D
のいずれかのようです。
*
や #
は電話機のボタン等でも見たことがありますね。
+
は国際電話をかけるときに先頭につけます。
A B C D
は何に使うのかよくわかりません😅
これらの他に ,
を入れることもできて、,
のタイミングでポーズ(一時停止)させることができます。
たとえば ATD123,456
という AT コマンドを実行すると、123
に電話をかけ、しばらく待ってから 456
を入力するようなイメージです。
サポートセンターなどに電話をすると自動音声でメニューが読み上げられて番号を入力させるようなことがあると思いますが、そのような場合に使えるのかもしれません。
,
でどのくらいの時間ポーズするかは ATS8
コマンドで設定することができます。
ATD
は番号の末尾に ;
で区切って電話に応答するための A
コマンドをつなげることができます。(例) ATD123456789;A
これは、ダイヤルアップしてからコールバックしてくるようなシステムで用いるようです。
だいぶ話がそれてしまいました。
ATD
や ATS8
のように、AT
のすぐあとにアルファベットがくるコマンドは「基本コマンド」といって、以下のような種類があります。
コマンド | 意味 |
---|---|
A |
かかってきた電話に出る |
D |
電話をかける |
E |
実行したコマンドをエコーバックするかどうかの設定 |
H |
Hook の制御 |
I |
識別情報のリクエスト |
L |
モニタースピーカーの音量設定 |
M |
モニタースピーカーのモード設定 |
O |
オンラインデータ状態に戻る |
P |
パルスダイヤリングを選択 |
Q |
結果コードの抑制 |
S0 |
自動応答の設定 |
S10 |
自動切断ディレイ |
S3 |
コマンド行終端文字 |
S4 |
レスポンスフォーマッティング文字 |
S5 |
コマンド行編集文字の設定 |
S6 |
ダイヤルトーン検出が無効の場合にダイヤル開始するまでの待ち時間の設定 |
S7 |
接続完了までのタイムアウト設定 |
S8 |
ダイヤル時に , でポーズする時間の設定 |
T |
トーンダイヤリングを選択 |
V |
DCE レスポンスフォーマット |
X |
結果コード選択およびコール進捗モニタリング制御 |
Z |
デフォルトコンフィグレーションへのリセット |
他にも、AT
のすぐあとに &
が来る基本コマンドもいくつかあります。
コマンド | 意味 |
---|---|
&C |
Circuit 109 (Received line signal detector) の挙動 |
&D |
Circuit 108 (Data terminal ready) の挙動 |
&F |
工場出荷時設定にリセット |
AT
のあとに +
が来るのは拡張コマンドと呼ばれ、+
の次の文字で大まかなコマンドのグループが特定され、さらに続く文字列でコマンドとなります。
拡張コマンドの先頭文字 | 意味 |
---|---|
+A |
呼制御関連 |
+C |
デジタル携帯電話拡張 |
+D |
データ圧縮 (ITU-T Rec. V.42 bis) |
+E |
エラー制御 (ITU-T Rec. V.42) |
+F |
FAX (ITU-T Rec. T.30 など) |
+G |
一般的なコマンド(identity や capabilities など) |
+I |
DTE-DCE インターフェース関連 (ITU-T Rec.v.24 など) |
+M |
変調方式 (ITU-T Rec. V.32 bis など) |
+P |
PCM DCE コマンド (ITU-T Rec. V.92) |
+S |
Switched or Simultaneous Data Types |
+T |
テスト関連 |
+V |
音声通話拡張 |
+W |
無線拡張 |
たとえば AT+CGDCONT=1,"IP","soracom.io"
は +C
なので携帯電話回線用の拡張コマンドで、PDP Context と呼ばれるものを定義するためのものです。詳しくは続編で解説することになると思います。
このあと AT コマンドを実際に実行したりして学んでいきたいと思いますが、その前に AT コマンドを実行してみることができる環境を準備することが必要です。
次の記事では、AT コマンド実行環境の準備をしていきます。
date コマンドつらい
タイトルどおりですが、date コマンド色々つらいですよね。
同じ名前の date
コマンドが、OS や環境によって、似て非なるものであるという辛さがまず第一に挙げられるかと思います。
macOS 上でデフォルトで使える date コマンドは BSD 版の date です。
BSD 版かどうかは man date
で確認できます。
DATE(1) BSD General Commands Manual DATE(1) NAME date -- display or set date and time :
BSD という文字が見えますね。
一方、Ubuntu などの Linux 上では GNU coreutils 版の date コマンドが使えます。(Mac にも homebrew などで GNU coreutils をインストールすることもできますが、話がややこしくなるのでここでは一旦おいておきます)
こちらは date --help
で確認してみましょう。
: Report date bugs to bug-coreutils@gnu.org GNU coreutils home page: <http://www.gnu.org/software/coreutils/> General help using GNU software: <http://www.gnu.org/gethelp/> For complete documentation, run: info coreutils 'date invocation'
最後の方に GNU coreutils という文字列が見えるはずです。
これらの date コマンドのオプションは、共通なものもあればまったく異なるものもあり、時々ハマリます。 (たとえばローカルの Mac で動いてたシェルスクリプトが Linux サーバー上で動かない等)
さらに、Linux の中でも Docker 用に最適化された Alpine などでは GNU coreutils 版ではなく BusyBox 版の date がインストールされています。 この BusyBox 版の date は GNU coreutils 版に似たオプションが使えますが、機能が削減されていたりして、これもまた別物と考えたほうがよさそうです。
BusyBox 版の date かどうかは以下のように確認できます。
$ date --help BusyBox v1.26.2 (2017-02-21 17:41:44 GMT) multi-call binary. :
さて、たとえば単純に現在の日時を表示させるだけならどの date を使っていても、単に date
と入力すれば良いだけです。
BSD date
$ date Fri Jun 16 09:41:37 JST 2017
$ date Fri Jun 16 00:41:48 UTC 2017
BusyBox date
$ date Fri Jun 16 00:42:05 UTC 2017
表示されるフォーマットも同じですね。
では、次に年月日だけを指定のフォーマットで表示させてみましょう。
BSD date
$ date +"%Y-%m-%d" 2017-06-16
$ date +"%Y-%m-%d" 2017-06-16
BusyBox date
$ date +"%Y-%m-%d" 2017-06-16
これは同じ引数で同じ結果となります。
では、昨日(現在日時の1日前)の日付を表示させてみましょう。
BSD date
$ date -v-1d Thu Jun 15 09:50:02 JST 2017
$ date -d '1 day ago' Thu Jun 15 00:53:07 UTC 2017
BusyBox date
$ date -d "1970.01.01-00:00:$(( $( date +%s ) - $(( 24 * 60 * 60 )) ))" Thu Jun 15 00:54:03 UTC 2017
BSD 版と GNU coreutils 版で、オプション名(-v と -d)も違いますし、そこに指定する値についてもだいぶポリシーが違いそうに見えます。BusyBox 版は発狂しそうですね。
一応 BusyBox 版を解説してみましょう。 BusyBox 版の -d オプションは GNU coreutils 版のような便利な(human friendly な)指定ができず、自力で計算している感じです。 基本的には Unix time で現在時刻から 1 日分の秒数を引き、それをエポックタイムに足して、1970年1月1日0時0分1497574657秒、みたいな時刻を作ってそれを表示させている感じです。(あと、厳密なことを言うと、うるう秒が挿入された日、タイムゾーンによっては夏時間になった日や夏時間が終わった日あたりにこのコマンドを実行すると、タイミングによっては誤った日時を表示してしまうかもしれません)
他にもタイムゾーンの指定とか、フォーマットの変換のときなどにどの date を使っているのかを勘違いすると全然思ったように動かなかったり、複数の環境で動作するようなスクリプトを書こうとしたりするとこのあたりの統一感のなさの辛さを味わってしまう感じです。
- 日付の計算(x 日後、とか)
- 日付のフォーマットの変換
などの date コマンドの機能のサブセットに特化した小さなバイナリを Golang あたりで複数の環境向けに作ると幸せになれるのかな、、、などと考えているところです。
Gnu Coreutils: Core Gnu Utilities
- 作者: David Maxkenzie
- 出版社/メーカー: Samurai Media Limited
- 発売日: 2015/11/11
- メディア: ペーパーバック
- この商品を含むブログを見る
Using BusyBox (Digital Short Cut) (Prentice Hall Open Source Software Development Series)
- 作者: Christopher Hallinan
- 出版社/メーカー: Prentice Hall
- 発売日: 2006/07/21
- メディア: Kindle版
- この商品を含むブログを見る
Arduino で SORACOM SIM を使ってなるべく安く通信する方法
[追記 2019/09/11]
Wio 3G SORACOM Edition や Wio LTE M1/NB1 といったラインナップも増えて、ますます安くセルラー通信機能付きの Arduino が使えるようになりました。
[追記 2019/09/11 おわり]
[追記 2017/10/30, リンク修正 2019/09/11]
SORACOM から Wio LTE JP Versoin が発売されたのでこの記事の内容にしたがって頑張るよりも安く(1万円以下で) Arduino で通信できるようになっちゃいましたね!
当記事には AT コマンドなど Wio LTE 他のデバイスでも使えたりしそうな情報がまだあると思いますし、Wio LTE では 3G が使えないので SORACOM の Global SIM の LDV を使ったりする場合はまだ当記事の方法で頑張る必要があると思いますのでこのまま残しておきます。
[追記ここまで]
ここで「通信」とは 3G or LTE でのモバイルデータ通信、「なるべく安く」というのは、
- イニシャルコスト
- ランニングコスト
の両面で考えていきたいと思います。
といってもランニングコストに関しては、SORACOM SIM を使っている限りはデータ通信量が少なければそんなに高くはならないので、あとは通常の日本向けの SIM を使うか、最近発表された Low Data Volume を使うか、といった感じになると思います。
ということでイニシャルコストの部分ですが、これは概ね、必要なハードウェアを買い揃えるにはいくらかかるか、という問題と等しいと考えて良いでしょう。
Raspberry Pi を使う場合には、Raspberry Pi 本体に USB ドングルを挿せばハードウェア費用はトータルで 1万円〜2万円程度で 3G もしくは LTE で通信できるようになると思います。
しかし Arduino を使う場合は、USB ドングルを単純には使えないので、たとえば 3GIM という製品を使うことになるかと思います。
- 出版社/メーカー: TABrain
- メディア:
- この商品を含むブログを見る
こちらは便利で使いやすい製品なのですが、お値段のほうもなかなかです。
ハードウェアの部分で悩まずにサクッとプロトタイピングしたいという場合や、本格的な用途で製品化するというような場合は 3GIM は最適な選択肢の一つだと思います。
しかしたとえば予算の制限のある中で少しでも安くプロトタイピングしたいといった要望もあると思います。
そこでなんとかトータルで 1 万円くらいで収まらないかと頑張ってみました。
できればはんだ付けの必要なしに、単純に組み合わせたり接続したりするだけで済ませたかったのですが、どうしてもはんだ付け(しかもとびきり難しいやつ)を回避できませんでしたので、ちょっと一般人向けではなくなってしまいましたが。。。
用意するもの
Arduino M0 ¥3,240(税込み)
- 出版社/メーカー: スイッチサイエンス
- メディア:
- この商品を含むブログを見る
その条件を満たす中で一番安いのが M0 ではないかと思います。
通信モジュール UC20-G Mini PCIe ¥3,980 + 税
https://soracom.jp/products/uc20-g/soracom.jp
Mini PCIe で接続するタイプの通信モジュールです。
ソラコムのコンソールから購入できます。
アンテナ HW-MULTI-GA-RSMA ¥980 + 税
上記モジュールと適合したアンテナです。上記モジュールはこちらのアンテナで技適を取っているのでこのアンテナを使う必要があります。
こちらもソラコムのコンソールから購入できます。
Mini PCIe → USB 変換基板(SIM カードスロット付き) ¥956(税込み)
powerday WWAN/LTEモジュール用SIMカードスロット付きMini PCI-E to USBアダプタ
- 出版社/メーカー: Rcmodel
- メディア: Personal Computers
- この商品を含むブログを見る
USB Host Shield ¥2,263(税込)
USBホストシールド 2.0 for Arduino (compatible with Google Android ADK)
- メディア: エレクトロニクス
- クリック: 5回
- この商品を含むブログを見る
SORACOM Air SIM
SORACOM Air SIMカード(データ通信のみ) (ナノ)
- 出版社/メーカー: 株式会社ソラコム
- メディア: エレクトロニクス
- この商品を含むブログを見る
データ通信量が少ない方は、Low Data Volume 版を購入すると良いかもしれません。
blog.soracom.jp
こちらはソラコムのコンソールで購入します。(1枚5ドルですが、税と送料を合わせると 12ドルくらいになりますので、初期費用は普通の SIM とそれほど変わらないかもしれません)
(価格はいずれも 2017 年 6 月 1 日現在)
はんだ付けに自信のある方なら、これらを揃えることで Arduino で 3G で通信ができるようになります。
はんだ付けをどうしても回避したいという場合には、USB Host Shield 用のライブラリ*1 に、UC20-G を USB ポート経由で制御できるようになるコードを追加するという方法も考えられます。
ここまでで合計 1 万 2,000 円くらいかと思います。(送料別・消費税は一部別)
Arduino M0(もしくは M0 Pro などの条件を満たすもの) や SORACOM SIM を最初から持っていればその分安くなります。
なお、上記の商品の他に、Arduino を PC に接続したり電源を供給するための USB ケーブルや、ジャンパー線やはんだ付け用の道具一式、もし外で使うならモバイルバッテリーなども必要です。
それらはすでに持っているという場合が多いと思いますのでここでは割愛します。
それから、もし 3G ではなく LTE で通信したければ、UC20-G Mini PCIe の代わりに EC21-J Mini PCIe をご購入ください。あとアンテナももう一本追加する必要があります。合計1,300円ほど高くなりますが、3G の代わりに LTE 対応にできるはずです。
準備(はんだ付け)
さて、モノが揃ったら準備を始めましょう。準備と言っても、メインははんだ付けをすることです。
以下、ちょっと長くなりますが、まず「なぜはんだ付けが必要なのか」を説明します。
Arduino で 3G/LTE 通信モジュールを使おうと思ったら、何らかのインターフェースで Arduino と通信モジュールを接続しなければいけません。
通信モジュールは大抵、USB や UART などのインターフェースを持っています。
今回使おうとしている UC20-G Mini PCIe に搭載されているチップ(UC20-G)の ハードウェアデザイン を見ると、このチップも USB と UART のインターフェースを持っています。
USB インターフェース経由でこのモジュールを使おうと思うと、Arduino の場合は USB Host Shield を用いて接続し、さらにドライバのコードを書かなければいけません。USB Host Shield 用のライブラリには、UC20-G に適合するドライバが標準では含まれていないように思われるためです。(4種類くらい入っているドライバを試しましたが、いずれも動作しませんでした)
ソフトウェアエンジニアとしては UC20-G 用のドライバを書く方向で解決したかったのですが、ドライバを書くための各種情報が少なく、いったん断念しました。
ということで USB インターフェースを使うのは難しそうなので、UART インターフェースを使うことを考えます。
UC20-G の UART インターフェースは、デフォルトでは 115200 bps のボーレートで通信するようです。
一方、Arduino の GPIO ピンを用いて SoftwareSerial で通信しようと思うと、最大通信速度は 115200 bps に対応していない模様です。
AT コマンドを送ればこの設定を変更してもっと低い速度でも通信できるようになるようですが、そもそも接続できなければ設定変更のしようもありません。
他の手段で(たとえば PC などに接続して)AT コマンドを送って UART の速度を変更してから Arduino につなぎ直しても良いのですが、万が一デフォルト値の 115200bps にリセットされてしまった場合などに再度設定し直すという面倒なことをしなければいけなくなります。
それであれば、最初からハードウェア UART インターフェースを搭載していて 115200bps にも問題なく対応できる Arduino を使ったほうが良いだろう、という判断です。
また、UC20-G の UART は 3.3V 駆動なので、いずれにせよ私の持っていた 5V 駆動の Arduino ではレベルシフタなどを使って 3.3V に降圧する必要があります。
ということで、ハードウェア UART インターフェース搭載かつ 3.3V 駆動の Arduino を使い、UC20-G の UART インターフェースに接続することにしました。
あとは、Arduino のハードウェア UART のピンと、UC20-G の UART のピンを接続するだけです。UC20-G のチップは下の図のようなピン配置になっています。
しかし、実物のモジュールを見ていただくとわかると思うのですが、ピンの露出している部分が非常に小さく、はんだ付けが難しそうに感じました。
一方、Mini PCIe のピン配置は以下のようになっています。
こちらは Mini PCIe → USB 変換基板のコネクタに嵌合させる部分なのでここにはんだ付けはできません。
したがって Mini PCIe → USB 変換基板のコネクタ側にはんだ付けをすることを考えます。
こちらはピンのピッチが非常に狭く、手作業ではんだ付けするのは至難の業に思われました(実際にとても難しく、何度か挑戦してやっと成功しました)が、万が一失敗して壊してしまうようなことがあっても1,000円程度で買い直すことができるということで多少精神的な障壁が低かったのでこちらにはんだ付けすることにしました。
決してきれいなはんだ付けではありませんが😅、これでもなんとか動いています。ホットボンドなどで固定&絶縁したほうがよいかもしれません。
すべて組み合わせるとこのような感じになります。
AT コマンド投入!
さて、これで実際にソフトウェアを動かしてみましょう。
PC で Arduino の IDE を立ち上げて、以下のコードを入力します。
#define UC20UART (Serial1) void setup() { SerialUSB.begin(9600); delay(3000); SerialUSB.println("Connecting ... "); UC20UART.begin(115200); while (!UC20UART) { ; } UC20UART.println("AT"); } void loop() { if (UC20UART.available()) { SerialUSB.write(UC20UART.read()); } if (SerialUSB.available()) { UC20UART.write(SerialUSB.read()); } }
これをコンパイルしてアップロードします。
このコードは、PC 側との接続に使っているシリアルポートと、UC20-G との接続に使っているシリアルポートを相互に接続しているようなイメージです。つまり、Arudino IDE の Serial Monitor で入力した文字列は UC20-G に送られ、UC20-G からの応答は Serial Monitor に表示されます。
アップロードが完了したら、Serial Monitor を立ち上げます。
Connecting ... AT OK
と表示されていたら成功です!
改行コードを CR+LF を送るように変更し(Both NL & CR を選択)、入力欄から AT コマンドを送るようにしてみましょう。
SORACOM の Global 向けの Plan 01 Low Data Volume の SIM を用いる前提で、以下のようにコマンドを入力すると良いでしょう。
AT+QCFG="roamservice",2,1 AT+COPS=1,2,"44010"2 AT+QICSGP=1,1,"soracom.io","sora","sora",0 AT+QIACT=1
ここまで実行し、すべて OK というレスポンスが返ってきたとすると、コンソール画面上で SIM がオンラインになっているはずです!
あとは
AT+QIOPEN=1,0,"TCP","harvest.soracom.io",8514 AT+QISEND=0 {"a":1,"b":2} (Ctrl-z) AT+QICLOSE=0
とかをスケッチから送信するようにすれば、Harvest にデータを送ることができます。
(Ctrl-z を Serial Monitor から送る方法がわからなかったのでスケッチの中に UC20UART.write(0x1a) と書きました)
Harvest にデータが届いた様子:
同様に Beam や Funnel も使えそうですね。
AT コマンドのリファレンスは、Quectel のサイトからダウンロードできますので、それぞれどういう意味か確認してみてください。
ということで駆け足になりましたが Arduino で SORACOM SIM をなるべく安く使う方法(ただしはんだ付けができる人に限る)でした!
- 作者: 佐竹正宏
- 発売日: 2016/10/06
- メディア: Kindle版
- この商品を含むブログを見る
goot 電子工作用はんだこて セット 安定感抜群の大型コテ台付き 半田こて はんだ吸い取り線 はんだ 40W BM-40S
- 出版社/メーカー: 太洋電機産業
- メディア: Tools & Hardware
- この商品を含むブログを見る
- 出版社/メーカー: nobrand
- メディア: エレクトロニクス
- この商品を含むブログを見る
SORACOM LoRa Space に「接続ポイント」を登録してみた!
SORACOM の共有型 LoRaWAN 接続サービス「SORACOM LoRa Space」ですが、私の手元にもようやく、ようやくデバイス(LoRa Arduino 開発シールド AL-050)が届きまして、さてどんな事をして遊ぼうかなと考えていたところ、SORACOM LoRa Space のホームページ が開設され、LoRaWAN でつながった箇所をみんなで教え合うみたいなことができるようになりましたので早速試してみました。
ログインして「接続ポイントの登録」画面に行き、説明に従ってセットアップしていくと・・・
こんな感じでプロットされます。 (↑はソラコム二子玉川オフィスの周辺です。)
セットアップは拍子抜けするほど簡単だったので、皆さんもぜひ試してみてはいかがでしょうか。
ちなみに私の使用した Arduino は STEMTera というもので、Arduino とブレッドボードが一体化したというものすごく便利なものです。
STEMTera Breadboard - Arduino 内蔵 ブレッドボード (02 ホワイト)
- 出版社/メーカー: STEMTera
- メディア: エレクトロニクス
- この商品を含むブログを見る
Arduino を使っていると、結局配線したりするのにブレッドボードが必要になるので、この組み合わせは最強ですね。 しかも、うれしいことにこの STEMTera、裏面が LEGO 互換・・・!私の Mac の Brik Book との相性も抜群です。
↓の共立エレショップさんで買うと Amazon より安いです。
これに、GPS モジュールを接続します。
こんな感じです。
そしてモバイルバッテリーを接続して、いざお出かけ。
ちなみにこの基板やら配線やらむき出しのブツを持って街中をウロウロしてると通報されかねませんのでそのあたりは自己責任でお願いします。私は自宅から持参したタッパーに入れて持ち歩きました。
Go のプログラムで Windows 上で chmod しようとしただけなのになぜか golang.org/x/sys/windows にバグっぽい挙動を見つけたばかりかなぜかアセンブラを読むハメになった話
何を言ってるかわか(ry
ファイルのパーミッションを変更する chmod
コマンド相当の関数は、私の知る限り Golang の標準ライブラリには 2 つあって、しかもそのいずれもが Windows 上では期待したような動作にならないって、みなさん知っていましたか?
まずひとつめ、os.Chmod() 、これは内部的に syscall.Chmod()
を呼んでいます。
syscall.Chmod()
は このあたり に実装があって(本記事執筆時点)、ソースコードをご覧頂いてもわかるように Windows API の SetFileAttributes()
を呼んでファイルの ReadOnly 属性を設定したり落としたりしているだけです。
魚拓:
なので、os.Chmod(path, 0600)
とかやっても、もともと他の人が読み書きできる設定になってるファイルはそのまま他の人が読み書きできる状態が維持されてしまいます。
びっくりしますね。
続いてふたつめ、 func (*os.File) Chmod() です。これは内部的に (*poll.FD) Fchmod()
を呼んでいて、そこから syscall.Fchmod()
が呼び出されます。こちらの実装は常に EWINDOWS
という値を返すようになっています。つまり常にエラーです。
魚拓:
なので、var f *os.File
な f
に対して f.Chmod(0600)
とかやっても、エラーになるだけでファイルのパーミッションは何も変わりません。
このあたりで、「Windows には ACL (Access Control Lists) というものがあったな・・・POSIX/UNIX のシンプルなファイルパーミッションとは根本的に仕組みが違うのかな・・・」ということに気づいちゃいます。
ということで、「じゃあ Golang で Windows の ACL が操作できるライブラリがあればいいじゃないか。きっとあるはずだ。」と思っておもむろにグーグル先生に聞いてみるわけじゃないですか。
するといくつかライブラリが見つかりますが、私がやりたいことができそうだったのが以下のライブラリです。
しかも chmod.go という名前の、そのものズバリなファイル(そしてその中に Chmod()
関数)まであるじゃありませんか。
ということでこいつを使ってみます。
すると、ほぼ期待したような動作をしてくれるようです。 「いいじゃないか、こいつを採用だ」と思った時に、一つ問題が見つかりました。
この go-acl の Chmod()
は、エラーが起きたとき(たとえば存在しないファイルに対して呼び出したときなど)に、error
を返してくれるのは良いのですが、error.Error()
でそのエラーメッセージを取り出すと、常に
The operation completed successfully.
という文字列が取得されるのです。
なぜだ、、、
ということで Deep dive が始まります。
go-acl の Chmod()
は内部で同じパッケージの Apply()
を呼んでいます。
Apply()
は内部で go-ac/api パッケージの GetNamedSecurityInfo()
や SetNamedSecurityInfo()
を呼び出します。
ここでは GetNamedSecurityInfo()
を見ていくことにしましょう。
// https://github.com/hectane/go-acl/blob/master/api/secinfo.go#L44 var ( procGetNamedSecurityInfoW = advapi32.MustFindProc("GetNamedSecurityInfoW") procSetNamedSecurityInfoW = advapi32.MustFindProc("SetNamedSecurityInfoW") ) // https://msdn.microsoft.com/en-us/library/windows/desktop/aa446645.aspx func GetNamedSecurityInfo(objectName string, objectType int32, secInfo uint32, owner, group **windows.SID, dacl, sacl, secDesc *windows.Handle) error { ret, _, err := procGetNamedSecurityInfoW.Call( uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(objectName))), uintptr(objectType), uintptr(secInfo), uintptr(unsafe.Pointer(owner)), uintptr(unsafe.Pointer(group)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(sacl)), uintptr(unsafe.Pointer(secDesc)), ) if ret != 0 { return err } return nil }
advapi32.dll からロードした GetNamedSecurityInfoW()
Windows API を呼んでいます。
advapi32 は windows.MustLoadDLL()
でロードした windows.DLL
型なので、advapi32.MustFindProc()
でロードした procGetNamedSecurityInfoW
は windows.Proc
型です。
procGetNamedSecurityInfoW.Call()
の 3 番目の戻り値に問題がありそうです。
windows.Proc
の Call()
関数は以下のような定義になっています。
// https://github.com/golang/sys/blob/master/windows/dll_windows.go#L126 func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { switch len(a) { case 0: return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0) case 1: return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0) case 2: return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0) :
syscall.Syscall()
を呼んでいますね。
// https://github.com/golang/go/blob/master/src/runtime/syscall_windows.go#L156 //go:linkname syscall_Syscall syscall.Syscall //go:nosplit func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) { c := &getg().m.syscall c.fn = fn c.n = nargs c.args = uintptr(noescape(unsafe.Pointer(&a1))) cgocall(asmstdcallAddr, unsafe.Pointer(c)) return c.r1, c.r2, c.err }
この 3 番目の戻り値 c.err
が怪しいです。
ちなみに、ここでは関数シグネチャでは 3 番目の戻り値の型は uintptr
になっていますが、この関数の宣言部では以下のように syscall.Errno
になっています。
// https://github.com/golang/go/blob/master/src/syscall/dll_windows.go#L24 // Implemented in ../runtime/syscall_windows.go. func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
さて、syscall_Syscall()
の中に戻って見ていきます。
この3番目の戻り値 c.err
が設定されているのは、おそらく cgocall()
の中でしょう。
少し長いですが cgocall()
関数の全体を以下に掲載します。
// https://github.com/golang/go/blob/master/src/runtime/cgocall.go#L92 // Call from Go to C. //go:nosplit func cgocall(fn, arg unsafe.Pointer) int32 { if !iscgo && GOOS != "solaris" && GOOS != "windows" { throw("cgocall unavailable") } if fn == nil { throw("cgocall nil") } if raceenabled { racereleasemerge(unsafe.Pointer(&racecgosync)) } // Lock g to m to ensure we stay on the same stack if we do a // cgo callback. In case of panic, unwindm calls endcgo. lockOSThread() mp := getg().m mp.ncgocall++ mp.ncgo++ mp.incgo = true // Reset traceback. mp.cgoCallers[0] = 0 // Announce we are entering a system call // so that the scheduler knows to create another // M to run goroutines while we are in the // foreign code. // // The call to asmcgocall is guaranteed not to // grow the stack and does not allocate memory, // so it is safe to call while "in a system call", outside // the $GOMAXPROCS accounting. // // fn may call back into Go code, in which case we'll exit the // "system call", run the Go code (which may grow the stack), // and then re-enter the "system call" reusing the PC and SP // saved by entersyscall here. entersyscall(0) errno := asmcgocall(fn, arg) exitsyscall(0) // From the garbage collector's perspective, time can move // backwards in the sequence above. If there's a callback into // Go code, GC will see this function at the call to // asmcgocall. When the Go call later returns to C, the // syscall PC/SP is rolled back and the GC sees this function // back at the call to entersyscall. Normally, fn and arg // would be live at entersyscall and dead at asmcgocall, so if // time moved backwards, GC would see these arguments as dead // and then live. Prevent these undead arguments from crashing // GC by forcing them to stay live across this time warp. KeepAlive(fn) KeepAlive(arg) endcgo(mp) return errno }
直接 c.err
に何か値を設定しているところはないようです。
errno := asmcgocall(fn, arg)
の行が怪しいですね。
ここで fn
は、cgocall()
を呼び出す際に第一引数に指定された asmstdcallAddr
です。
asmstdcallAddr
は以下のように定義されていて、
// https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L157 var asmstdcallAddr unsafe.Pointer
以下のように初期化されています。
// https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L263 func osinit() { asmstdcallAddr = unsafe.Pointer(funcPC(asmstdcall))
asmstdcall
という関数のアドレスのようです。
asmstdcall
は
// https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L153 // Call a Windows function with stdcall conventions, // and switch to os stack during the call. func asmstdcall(fn unsafe.Pointer)
というように宣言だけされていますが、実体はアセンブラで記述されています。 名前の通り、stdcall 呼び出し規約に則って関数を呼び出す処理がアセンブラで記述されているようです。
以下は 64bit windows 環境用のアセンブリコードです。
// https://github.com/golang/go/blob/master/src/runtime/sys_windows_amd64.s#L13 // void runtime·asmstdcall(void *c); TEXT runtime·asmstdcall(SB),NOSPLIT|NOFRAME,$0 // asmcgocall will put first argument into CX. PUSHQ CX // save for later MOVQ libcall_fn(CX), AX MOVQ libcall_args(CX), SI MOVQ libcall_n(CX), CX // SetLastError(0). // https://en.wikipedia.org/wiki/Win32_Thread_Information_Block // http://www.geoffchappell.com/studies/windows/win32/ntdll/structs/teb/index.htm // http://shitwefoundout.com/wiki/Win32_Thread_Environment_Block MOVQ 0x30(GS), DI //* DI = _NT_TIB.Self (where GS == TIB: Thread Information Block) MOVL $0, 0x68(DI) //* _TEB.LastErrorValue = 0 SUBQ $(maxargs*8), SP // room for args // Fast version, do not store args on the stack. CMPL CX, $4 JLE loadregs // Check we have enough room for args. CMPL CX, $maxargs JLE 2(PC) INT $3 // not enough room -> crash // Copy args to the stack. MOVQ SP, DI CLD REP; MOVSQ MOVQ SP, SI loadregs: // Load first 4 args into correspondent registers. MOVQ 0(SI), CX MOVQ 8(SI), DX MOVQ 16(SI), R8 MOVQ 24(SI), R9 // Floating point arguments are passed in the XMM // registers. Set them here in case any of the arguments // are floating point values. For details see // https://msdn.microsoft.com/en-us/library/zthk2dkh.aspx MOVQ CX, X0 MOVQ DX, X1 MOVQ R8, X2 MOVQ R9, X3 // Call stdcall function. CALL AX ADDQ $(maxargs*8), SP // Return result. POPQ CX MOVQ AX, libcall_r1(CX) // GetLastError(). MOVQ 0x30(GS), DI //* DI = _NT_TIB.Self MOVL 0x68(DI), AX //* AX = _TEB.LastErrorValue MOVQ AX, libcall_err(CX) RET
このアセンブラは Go の処理系特有の記法で、この文法などについての最も詳しい説明は以下のリンク先のようです。
A Manual for the Plan 9 assembler
Rob Pike 先生が自ら書かれた説明のようですね。
多少特殊な記法はありますが、Intel 系のアセンブラに慣れた人なら特に問題なく読めるでしょう。
この最後の
// GetLastError(). MOVQ 0x30(GS), DI //* DI = _NT_TIB.Self MOVL 0x68(DI), AX //* AX = _TEB.LastErrorValue MOVQ AX, libcall_err(CX)
の部分です。
//*
で始まるコメントは私が追加したものです。
早い話が、Thread Information Block (TIB) またの名を Thread Environment Block (TEB) の LastErrorValue を取り出して、libcall.err
に入れています。
libcall
というのは↓のことで、
// https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L295 type libcall struct { fn uintptr n uintptr // number of parameters args uintptr // parameters r1 uintptr // return values r2 uintptr err uintptr // error number }
これは m
のメンバです。
type m struct { : syscall libcall // stores syscall parameters on windows : }
m
は g
のメンバです。
// https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L320 type g struct { : m *m // current m; offset known to arm liblink :
m
とか g
とかについては ↓ このあたりに良い説明があります。
要は、
// https://github.com/golang/go/blob/master/src/runtime/syscall_windows.go#L156 //go:linkname syscall_Syscall syscall.Syscall //go:nosplit func syscall_Syscall(fn, nargs, a1, a2, a3 uintptr) (r1, r2, err uintptr) { c := &getg().m.syscall
この c
は g
の m
の syscall
すなわち libcall
型なので先程のアセンブラ
// GetLastError(). MOVQ 0x30(GS), DI //* DI = _NT_TIB.Self MOVL 0x68(DI), AX //* AX = _TEB.LastErrorValue MOVQ AX, libcall_err(CX)
では c.err
に LastError
を入れてますよ、ということになります。
ちなみに TIB のメモリレイアウトに関しては以下のリンク先を参考にしました。
Win32 Thread Environment Block - Shit we found out
さて、そういうわけで procGetNamedSecurityInfoW.Call()
の第 3 戻り値には LastError が入っていそうなものです。
Errno
型なので、以下の関数によって string
に変換されるはずです。
// https://github.com/golang/go/blob/master/src/syscall/syscall_windows.go#L90 func (e Errno) Error() string { // deal with special go errors idx := int(e - APPLICATION_ERROR) if 0 <= idx && idx < len(errors) { return errors[idx] } // ask windows for the remaining errors var flags uint32 = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_IGNORE_INSERTS b := make([]uint16, 300) n, err := formatMessage(flags, 0, uint32(e), langid(LANG_ENGLISH, SUBLANG_ENGLISH_US), b, nil) if err != nil { n, err = formatMessage(flags, 0, uint32(e), 0, b, nil) if err != nil { return "winapi error #" + itoa(int(e)) } } // trim terminating \r and \n for ; n > 0 && (b[n-1] == '\n' || b[n-1] == '\r'); n-- { } return string(utf16.Decode(b[:n])) }
事前に定義済みの errors
の中には “The operation completed successfully” という文字列は見つかりませんでしたので、おそらく formatMessage()
関数がその文言を常に返してきてしまっているという状況なのでしょう。
formatMessage()
は
// https://github.com/golang/go/blob/master/src/syscall/syscall_windows.go#L144 //sys formatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW
という感じで Windows API の FormatMessageW
を呼び出しているだけです。
ということは e
が 0 なのか、、、
第 1 戻り値はちゃんとエラーの理由を表す値になっているようなので、もしかしたら GetNamedSecurityInfoW()
Windows API は LastError を設定しない君なのでは?という可能性に気づき、やる気が萎えたのでここでおしまい。
ということでタイトルでは「バグっぽい」と煽り気味に書きましたが、Golang のライブラリのバグではなくて、Windows の仕様なのかもしれません。
リモートホスト上のパケットキャプチャをリアルタイムに手元のマシンの Wireshark で見る方法
ワンライナーで行けちゃいます😁
wireshark -k -i <(ssh user@host "tcpdump -U -n -w - -i eth0 'not port 22'")
しかもリモートホスト側で何かしら特別な準備とかが不要なので非常にお気楽・お手軽です。
前提条件
- 手元の環境に Wireshark がインストールされており、コマンドラインから起動できる(上のワンライナーでは
wireshark
コマンド) - リモートホストに
ssh
でログインできる(踏み台を経由するなどの場合は~/.ssh/config
でよしなに設定するか、頑張って引数を指定する) - リモートホストに
tcpdump
がインストールされていて実行できる
解説
上記ワンライナーを実行すると、以下の図のように ssh
で接続したホスト上で tcpdump
コマンドを実行してパケットをキャプチャし、そのパケットキャプチャを手元の Wireshark でリアルタイムに見ることができるようになります。
Icons for a laptop and a server are designed by Gregor Cresnar from Flaticon
ワンライナーは比較的短いので難しくないと思いますが念のため解説します。
まず wireshark
コマンドで Wireshark を起動しています。
オプションの -k
はすぐにキャプチャを開始する、という意味です。
-i
は入力インターフェースの指定ですね。この場合は <( ... )
で指定されるコマンドの出力を Wireshark が読み込むという動きになります。
<( ... )
は bash の記法です。bash を用いていない場合は -i -
と pipe を組み合わせて
ssh user@host "tcpdump -U -n -w - -i eth0 'not port 22'" | wireshark -k -i -
という感じにすればよいでしょう。(末尾の - に注意)
続いて ssh
部分に入っていきます。
user@host
はキャプチャを取りたい目的のリモートホスト(とそれにログインするためのユーザー)の指定です。
公開鍵認証をする場合や踏み台サーバー経由でのログインをする場合などはここに引数を追加するか、~/.ssh/config
に指定しておくとよいでしょう。
""
で囲まれた部分がリモートホスト上で実行されます。
つまり、tcpdump
がいろいろ引数を指定された状態で起動されます。
tcpdump
コマンドに渡されている引数を一つずつ見てみましょう。
-U
は 1 パケットごとに出力ファイルに出力するように指定するオプションです。(これを指定しないと tcpdump にバッファリングされてしまい、リアルタイム性が損なわれると思われます。)
-n
は IP アドレスをホスト名に逆引きする処理を抑制するオプションです。リモートホスト側で名前解決をしてしまうとパケットキャプチャにそれが混じってしまうので DNS クエリをしないようにこれを指定しています。
-w -
は標準出力へ出力することを指定するオプションです。リモートホスト上で tcpdump
が標準出力にパケットの情報を出力すると、それが ssh
経由でローカルホストの標準出力に出力されます。そしてその標準出力を wireshark
に食わせているというわけです。
-i eth0
は tcpdump
がパケットをキャプチャするインターフェース名の指定です。適宜他の名前に変えましょう。
'not port 22'
は tcpdump
でキャプチャするパケットを選別するためのフィルタの指定。ここでは、パケットキャプチャの転送に使われている 22 番ポートのパケットはキャプチャしないように指定しています。
bash スクリプトでヒアドキュメントを変数に格納したいけどヒアドキュメントの中にカッコがあってエラーになってしまう場合の対策
最近ブログの記事タイトルが長い気がするのでなんとか短めに抑えたいのですがなんとかならないものでしょうかね。
さて、昨年あたりから shellcheck というツールを使ってシェルスクリプトの lint をするようにしたので自分の shell 力が少し上がった気がしているのですが、以下のようなありがちなコードの解決策が覚えられそうになかったのでメモしておきます。
やりたいことは、read コマンドを使ってユーザーに複数の選択肢を提示して選んでもらうというとてもシンプルな、よくありがちなことだと思います。
read コマンドには -p オプションがあり、ユーザーに対して何かのプロンプトを提示することができます。
そこで
read -r -p "$prompt" num
という感じでプロンプトを表示しつつ num という名前の変数に選択されたの番号を取得することを考えます。
prompt 変数にはメッセージを入れておきます。
たとえば、以下のようなプロンプトを表示させたい場合:
1) hoge 2) fuga 3) piyo choose a number (1-3) ?
変数 prompt には定石パターンとして以下のように $( ) と cat とヒアドキュメントを使って、
prompt=$(cat <<EOD 1) hoge 2) fuga 3) piyo choose a number (1-3) ? EOD )
という感じにすると思います。しかしこれは実は問題があります。 ヒアドキュメントの中に半角閉じカッコが入っていると、そこで $( が閉じられてしまって、その後シンタックスエラーなどが起こってしまうのです。
なお、` 〜 ` を使うと閉じカッコの問題は起こらないかもしれませんが、
prompt=`cat <<EOD ^-- SC2006: Use $(..) instead of legacy `..`.
というふうに shellcheck に警告されてしまいます。 shellcheck の説明 を読むと、` 〜 ` には POSIX で未定義動作があったり、独自のエスケープモードに入ってびっくりするような結果になったり、ネストさせるのが難しかったり、といったような弊害があるとのことです。
というわけで $( ) を使って以下のように書いてみるとやはり実行時にエラーになってしまいます。
$ cat paren.sh #!/bin/bash prompt=$(cat <<EOD 1) hoge 2) fuga 3) piyo choose a number (1-3) ? EOD ) read -r -p "$prompt" num echo "You chose $num" $ bash paren.sh paren.sh: line 4: hoge: command not found paren.sh: line 5: syntax error near unexpected token `)' paren.sh: line 5: `2) fuga'
これを回避するには、関数 を使って以下のようにします。
function get-msg() { cat <<EOD 1) hoge 2) fuga 3) piyo choose a number (1-3) ? EOD } prompt=$( get-msg ) read -r -p "$prompt" num echo "You chose $num"
get-msg 関数(名前はなんでも良いです)が cat とヒアドキュメントを使って問題となる閉じカッコを含む文字列を表示します。 それを $( ) の中で 呼び出すことで文字列を変数に格納します。 これにより、$( ) の中に閉じカッコが現れなくなるので問題が起こらなくなるというわけです。
[改訂新版] シェルスクリプト基本リファレンス ??#!/bin/shで、ここまでできる (WEB+DB PRESS plus)
- 作者: 山森丈範
- 出版社/メーカー: 技術評論社
- 発売日: 2011/04/27
- メディア: 単行本(ソフトカバー)
- 購入: 9人 クリック: 119回
- この商品を含むブログ (10件) を見る
- 作者: Cameron Newham,Bill Rosenblatt,株式会社クイープ
- 出版社/メーカー: オライリージャパン
- 発売日: 2005/10/27
- メディア: 大型本
- 購入: 7人 クリック: 138回
- この商品を含むブログ (47件) を見る
- 作者: Carl Albing,JP Vossen,Cameron Newham,株式会社クイープ
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/09/26
- メディア: 大型本
- 購入: 8人 クリック: 268回
- この商品を含むブログ (31件) を見る
ssh の ProxyCommand を .ssh/config に書かずにコマンドライン引数で指定する方法
ssh で踏み台ホスト経由で目的のサーバーに到達したいということはよくあると思います。
たくさんの先人たちが ~/.ssh/config に書くべき内容について書き残してくれていますのでここでは特に詳しくは触れません。
基本的には ProxyCommand を使って、踏み台サーバーから目的のサーバーへと接続するような設定をすればよいのですが、この設定を ~/.ssh/config に書かずに、コマンドライン引数のみで実現できないかと思って調べてみました。
なぜそのように思ったかというと、たとえばあるサーバーに踏み台経由でログインするという処理をスクリプトにしてチームのメンバーに配布して使ってもらうような場合に、「まず .ssh/config を編集して・・・」というステップが入ってくるとそれだけで敷居が高くなってしまったり、間違いが入り込んでしまう余地が多くなってしまいます。 また、接続先の数が増えるとそのたびに編集してもらう必要が出てきてしまいますし、クラウドのようにホストのアドレスが動的に変わるような環境だと別途 API などによって IP アドレスを取得してくる必要があり、そもそも .ssh/config に static な設定を書きづらいということもあるかもしれません。
「単にスクリプトを実行すれば目的のサーバーに到達できる」というシンプルさを保つためには、可能であればスクリプトだけで完結する方法が望ましいわけです。
スクリプトで ~/.ssh/config を書き換えるという少々強引な手法も考えられますが、これは余計な問題を引き起こしそうな気がするのでちょっと避けたいところです。
そんなわけで man ssh
を眺めていたところ、-o
というオプションを発見しました。これは、~/.ssh/config に書くことができる内容をそのままコマンドライン引数として渡すことができるというオプションです。つまり、-o ProxyCommand=〜〜〜
というようなオプションを ssh コマンドに渡すことができそうです。
たとえば接続元ホスト(local) から踏み台サーバー (gateway) 経由で接続できるようなリモートサーバー (remote) があって、以下のように ~/.ssh/config に書いた場合、
Host gateway Hostname gateway.example.com User gateway-user Port 8022 IdentityFile ~/.ssh/id_rsa_gateway Host remote Hostname 10.11.12.13 User remote-user Port 1022 IdentityFile ~/.ssh/id_rsa_remote ProxyCommand ssh gateway -W %h:%p
ssh remote
とするだけでリモートサーバーにログインできます。
(IdentityFile はどちらも接続元のホスト (local) に置いておきます)
これと同じことをコマンドライン引数だけで実現すると以下のようになります。
ssh -o ProxyCommand='ssh -i ~/.ssh/id_rsa_gateway -p 8022 gateway-user@gateway.example.com -W 10.11.12.13:1022' -i ~/.ssh/id_rsa_remote -p 1022 remote-user@10.11.12.13
ProxyCommand オプションには、gateway を介して remote ホストの ssh のポートまで到達するための設定を書き、その後 remote ホストにログインするための設定を書くようなイメージですね。 (もちろんオプションの順番はこの通りに並べる必要はありませんが、このようにすることで見通しが良くなるような気がします)
上記のコマンド例は私の実際のスクリプトから抜き出してそれをここに貼ることのできるように書き換えていますのでその途中で間違いなどが入り込んでしまっているかもしれません。何かお気づきになったらお知らせください。
- 作者: Daniel J. Barrett,Richard E. Silverman,Robert G. Byrnes,小島肇,坂井順行,鹿田幸治,園田道夫,高橋基信,根津研介,宮本久仁男
- 出版社/メーカー: オライリー・ジャパン
- 発売日: 2006/11/22
- メディア: 大型本
- 購入: 2人 クリック: 79回
- この商品を含むブログ (48件) を見る
OpenSSH[実践]入門 (Software Design plus)
- 作者: 川本安武
- 出版社/メーカー: 技術評論社
- 発売日: 2014/11/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
git コマンドを alias してるときにも補完を効かせる方法
プログラマなら呼吸をするように git コマンドを使うと思うので、git っていう 3 文字でさえ長過ぎますよね。
ということでみなさま .bashrc などで
alias g='git'
みたいなことをしてると思いますが、この場合 git-completion.bash による補完が効かなくなってしまうという弊害がありました。
とはいえ、~/.gitconfig にサブコマンドの短縮形を大量に登録していると思いますし、ファイル名の補完は標準で効くので、不便なのはブランチ名の補完や滅多に使わないサブコマンドの補完など一部の作業に限られていました。
でもこれを解決する方法がありました。
.bashrc などで git-completion.bash を source した後に以下のように __git_complete 関数を実行しましょう。
source /Applications/Xcode.app/Contents/Developer/usr/share/git-core/git-completion.bash __git_complete g __git_main
私は Mac を使っていて Xcode に含まれている git-completion.bash を使っていますが、環境によって git-completion.bash のパスは異なると思います。
また、git のバージョンが変わったりすると関数名が変わったりするかもしれませんがその際は頑張って git-completion.bash を解読して、どの関数を呼べばよいか探しましょう。
これで .bashrc を source し直したら g コマンドでも補完が効くようになっています。
以上、自分用メモでした。
Mac で SORACOM Air で接続している時に特定のルートだけ SORACOM 経由にしてそれ以外は Wi-Fi 経由にする方法
タイトルが若干長いですが、やりたいことは以下のとおりです。
1. 通常は Wi-Fi などでインターネットに接続している
2. SORACOM Air 入りの USB 3G/LTE モデムを Mac に挿した時、SORACOM が提供するサービスには SORACOM Air 経由でアクセスしたいが、それ以外のネットワーク通信は通常通り Wi-Fi 側で行いたい
私の場合は soracom-sdk-go のテストの中で metadata.soracom.io へのアクセスがあり、そこだけ SORACOM Air 経由にしたいという個人的な事情がありました。
他にも SORACOM Beam や SORACOM Endorse、SORACOM Funnel などのサービスへのアクセスも同様にルーティングしたい場合があるかもしれませんし、SORACOM Canal や SORACOM Direct で接続したプライベート環境へのルーティングも同様に行いたい場合があるかもしれません。
設定手順はおおまかに以下の2ステップです。
1. システム環境設定 > ネットワーク で Wi-Fi と USB モデムの優先順位を設定
2. ダイアルアップ時に実行されるスクリプトを書いてルーティング設定を追加
ではスクリーンショットなどを交えながら具体的に説明していきます。
1. ネットワークインターフェースの優先順位を設定
システム環境設定からネットワークの設定を開きます。
左側のインターフェースのリストの下部に歯車アイコンがあり、それを押すとメニューが現れます。
その中から「サービスの順序」を選択します。
(画像はメニューなどが英語になっていますが日本語でも同様の項目があるはずです)
サービスの順序のリストが出てきますので、Wi-Fi などの通常使いたいものの方が上になるように設定します。(ドラッグアンドドロップで項目を移動させます)
並び替えたら OK を押して閉じます。
ちなみに上図の私の環境では FOMA L05A xxx というのが 3G モデムです。
- 出版社/メーカー: LG電子
- メディア: エレクトロニクス
- この商品を含むブログを見る
docomo データカード L-05A シルバー 携帯電話 白ロム LG電子
- 出版社/メーカー: LG電子
- メディア: エレクトロニクス
- この商品を含むブログを見る
以下のサイトの説明にしたがってセットアップしたものになります。
余談になるかもしれませんが、以下のサイトで説明されている手順が L-05A にも適用できて、CD を毎回 Eject する手間が省けます。
2. ダイアルアップ時のルーティング設定を追加
/etc/ppp/ip-up という名前のファイルを以下の内容で作成し、所有者: root 、ファイルのパーミッション:755 とします。
(ファイル名が if-up ではなく ip-up であることに注意してください)
#!/bin/sh /sbin/route add -net 100.64.0.0/10 10.64.64.64
この route コマンドにより、100.64.0.0/10 宛てのパケットを全て SORACOM Air のゲートウェイ(10.64.64.64)に向ける設定が行われます。
(ネットワークのアドレスは 100、ゲートウェイのアドレスは 10 で始まることに注意してください)
SORACOM の各サービスは 100.x.x.x というアドレス(ISP Shared Address)にて提供されています。
例えば SORACOM Beam(beam.soracom.io)は 100.127.127.100 というアドレスを持っていますし、SORACOM Air のメタデータサービスは 100.127.100.127 というアドレスでアクセスできます。
なお、ここで追加したルーティング設定は、disconnect 時に自動的に消えるようなので明示的に消さなくても良いようです。
これで beam.soracom.io などに ping が通れば成功です。
$ ping beam.soracom.io PING beam.soracom.io (100.127.127.100): 56 data bytes 64 bytes from 100.127.127.100: icmp_seq=0 ttl=64 time=399.464 ms 64 bytes from 100.127.127.100: icmp_seq=1 ttl=64 time=435.335 ms 64 bytes from 100.127.127.100: icmp_seq=2 ttl=64 time=394.338 ms :
万が一設定を失敗していると、SORACOM 宛てのパケットだけでなく全てのトラフィックが SORACOM 経由になってしまって課金が大変なことになってしまう恐れがありますので、ユーザーコンソールを開いて想定外のトラフィックが流れていないか目視で確認したり、イベントハンドラーを設定するなどして通信量を監視するとよいでしょう。
高度な設定としては、SORACOM Endorse へのトラフィックも SORACOM Air 経由にするために、上記スクリプト(/etc/ppp/ip-up)に以下のような設定を入れておくと良いでしょう。
endorse_ip=$( dig +noall +answer endorse.soracom.io | \grep -oE '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' ) /sbin/route add -host $endorse_ip 10.64.64.64
これは、endorse.soracom.io は 100.64.0.0/10 に属するアドレスを持っていないためです。dig コマンドで endorse.soracom.io のアドレスを解決して、そのアドレス宛てのルートを 10.64.64.64 へ向けています。
長時間かかったコマンドの実行完了を bash でも terminal-notifier でお知らせしたい
[2016.02.19 追記]
スクリプトにバグがあったので修正しました
[追記ここまで]
世の中には CLI から OS X の通知を出せる terminal-notifier という素晴らしいツールがありまして、
これを brew でインストールしたりなんかすると
$ terminal-notifier -message test
とかやるだけで
こんな感じにお知らせが出ます。
一方、何かコマンドを実行したら結果が出るまでに10分かかるとか1時間かかるとかそういうことってたまにありますよね。
「ビルドを開始したらちょっと休憩しよう」とか「デプロイを開始したらお昼ごはん行ってこよう」とか「これが終わるまで Twitter でも眺めてるか」ってこと、よくあると思います。
休憩したりお昼ごはんに行ってくるだけならだいたい目論見通りの時間で帰ってくることができますが、Twitter やら Facebook やらを見に行ったりしちゃったが最後、コマンドの実行はとっくに終わってるのになかなか帰ってこれなくて気づいたらすごい時間が経ってしまっていた・・・ってこともよくあると思います。
コマンドの実行が完了したらわかりやすく通知してくれればいいのに、と何度も思いました。
調べてみると、zsh では terminal-notifier を使ってそういうことを実現している方がたくさんいらっしゃるようです。
僕も zsh に改宗・・・と一瞬心が揺らぎましたが、bash でもできないかどうか調べてみました。
すると、こんなものを発見。
説明に書いてあるとおり、zsh の preexec と precmd をエミュレートするものです。
これがあれば、
preexec でタイムスタンプを保存
precmd でそのタイムスタンプと現在時刻の差を調べ、一定以上の時間が経っていたら terminal-notifier で通知を出す
という処理をすれば望んだ処理が実現できそうです。
ということで ~/.bashrc の末尾あたりに以下のようなコードを書いてみました。
[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh _tn_timestamp=`date +%s` _tn_cmd='' preexec() { _tn_timestamp=`date +%s` _tn_cmd=$1 } precmd() { now=`date +%s` dur=$(( $now - $_tn_timestamp )) if [[ $_tn_cmd == "" ]]; then return fi if [[ $dur -gt 60 ]]; then terminal-notifier -message "Finished: $_tn_cmd" echo elapsed time: $dur seconds fi _tn_cmd='' }
preexec はコマンド実行直前に呼ばれる関数で、precmd はコマンド完了後(より厳密には、プロンプト表示直前)に呼ばれる関数です。
プロンプトで何も入力せずに Enter だけ押すと preexec が呼ばれずに precmd が呼ばれたりしますのでそれを回避するコードが入ってたりします。
これで、実行開始して 60 秒以上経ってから実行が完了したら通知が表示されるようになりました。
しかし問題もあって、たとえば vim で何かのファイルを編集して 1 分以上経過して戻ってきたら通知が出ちゃったり、ssh で他のホストにログインして 1 分以上作業してからログアウトして戻ってきたりすると通知が出ちゃったりします。
ブラックリスト的に vim とか ssh だったら通知を出さないというような処理も入れる必要があるかもしれません。
今のところは vim や ssh の後に通知が表示されてもそれほど困ってないので放置してありますが、何か良い解決策をお持ちの方・思いついた方はお知らせいただければと思います。
SORACOM Air が使える USB 3G/LTE モデム、WiFi ルーターのまとめ
SORACOM Air SIMカード(データ通信のみ) (標準)
- 出版社/メーカー: 株式会社ソラコム
- メディア:
- この商品を含むブログを見る
SORACOM Air とは、ひとことで言うと IoT (Internet of Things) 向けの SIM です。
API で通信速度のコントロールができたり、データ通信量の情報を取得できたりする特徴があります。
SORACOM Air は docomo の MVNO の SIM なので、docomo 端末もしくは SIM フリー端末で使えるはずです。
スマホでも使えるみたいですが、ここでは IoT の実験をするときの本命として Raspberry Pi との組み合わせで使いやすい、USB モデム(USB ドングル)、WiFi ルーターをメインにまとめてみたいと思います。
以下、ネット上で動作報告があったデバイスを紹介していきますが、動作保証ではありませんのでそのあたりはご理解ください。
また記載した情報が間違っている可能性もあります。お気づきのことがありましたらコメント等でお知らせください。
文中で記載している価格は 2015年12月30日時点のものです。
続きを読む