ATコマンドを使って SORACOM に SMS を送信する

:::note info
この記事は [SORACOM Advent Calendar 2023](https://qiita.com/advent-calendar/2023/soracom) の13日目の記事です。
前日の記事は「[ArduinoやM5StackでSORACOMのメタデータサービスを使いこなそう](https://qiita.com/Plemling138/items/cd81713cb5fb943f5471)」でした。
:::

# はじめに

この記事では、セルラーモデムの AT コマンドを使って SMS を送信する方法について解説します。
SMS は送受信できるデータ量に対する単価でいうとIPパケット通信に比べてかなり割高なサービスではありますが、TCP/IP のスタックを持っていないデバイスからでも簡単にデータを送信できるとか、不到達時にネットワーク側がある程度リトライしてくれるとかいった利点があり、まだまだ使われる機会があるかと思います。

SMS は大昔から存在する技術なのでネット上の解説がたくさん見つけられるんですが、文字エンコーディングの取り扱いなどわかりにくい部分もあるので、仕様面を含むちょっと詳しめの理解に挑戦してみます。最近まで私自身も勘違いしていた部分がありハマってしまったのが、この記事を書こうと思ったきっかけです。

## 使用機材

この記事では、以下の SIM とセルラーデバイスを用いました

* SIM : [SORACOM IoT SIM](https://soracom.jp/services/air/cellular/) (planX1) 
* USB セルラー(LTE)モデム:  [SORACOM ONYX LTE USB ドングル](https://soracom.jp/store/7326/)

:::note warn
SMS に関して利用可能な機能や操作方法は、利用する SIM、通信キャリア、端末デバイスなどによって異なることがあります。また、本記事では SIM および SMS の送受信先に SORACOM を使用していますが、本記事と同一の結果が得られることを保証するものではありません。
:::


## 前提条件

セルラーデバイスについて、既にネットワークへの接続ができており、かつ AT コマンドが利用できる状態になっているものとします。SORACOM Onyx LTE USB ドングルを使って SORACOM Air for セルラーに接続して AT コマンドを利用する方法については、別記事の [SORACOM Onyx LTE USB ドングルの電波状況を SORACOM に保存する](https://qiita.com/shozoo/items/7d0a5d970c811cc50157) にも書いていますので、参考になりましたら幸いです！

# SMS メッセージ本文の仕様

後に出てくるATコマンドの意味の理解に必要なため、SMS メッセージ本文の標準仕様に詳しく踏み込んでおきます。

## SMS の標準仕様

SMS は当初第2世代の携帯電話である GSM 向けに開発され、その後の 3G、4G (LTE)、5G でも標準仕様を改訂しながらサポートが継続されています。標準化団体 3GPP の仕様書の中では、[3GPP TS 23.040](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=747) の規定が中心となります。
書籍では、 ["Mobile Messaging Technologies and Services: SMS, EMS and MMS, 2nd Edition"](https://learning.oreilly.com/library/view/mobile-messaging-technologies/9780470011430/)(G. L. Bodic 著) がとても詳細で参考になりました。

## SMS 送信メッセージ TPDU の構造

SMSの送信メッセージがネットワーク上で伝送される際には、下図に示すプロトコルデータユニット (TPDU) に格納されます。ここは雰囲気だけで大丈夫です！
ヘッダ部分では宛先のアドレス (TP-Destiination-Address）や本文で使用するエンコーディング方式 (TP-Data-Coding-Scheme) が指定されており、最後の TP-User-Data に SMS のメッセージ本文が格納されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960954/b94cf1f6-5b9c-f9f3-b09c-1f7fa76e98e5.png" width=60%>


### メッセージ本文(TP-User-Data)の構造

TP-User-Data 全体の長さは 0 ~ 140オクテット(1オクテット = 8bits)の可変長です。
TP-User-Data の先頭部分には、必要に応じて User-Data-Header (UDH) が配置されます（UDH も TP-User-Data の一部です）。UDH が存在する場合は、TPDU のヘッダ部分にある TP-User-Data-Header-Indicator (UDHI) がセットされます（フラグの値が'1'となります）。UDHも可変長です。
UDH が存在しない時には、最大で 140 オクテット分のデータが送受信できるということになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960954/b23cc3d5-900a-ee49-6245-4b0f6da18e68.png" width=70%>


## メッセージ本文の符号化方式

TP-Data-Coding-Scheme (DCS) で指定可能な符号化方式は以下の3種類です。

| 形式    | 符号化方式 | 最大データ長 |
|--------|------------------|------------|
| テキスト | GSM 7 bit default alphabet [[3GPP TS 23.038]](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=745) | 160 文字 |
| テキスト | UCS2 [[ISO/IEC 10646]](https://ja.wikipedia.org/wiki/ISO/IEC_10646)  | 70 文字 |
| バイナリ | 8-bit data                                   | 140 オクテット |

テキストメッセージの場合は、GSM 7bit default alphabet (以下GSM)か、UCS2 のどちらかということになります。GSM は 1 通で送受信可能な文字数が多い代わりに、利用可能な文字が限定される。UCS2は日本語を含む多くの文字を利用可能であるが、文字数が少なくなるというトレードオフがあります。

### GSM 7 bit default alphabet (GSM)

GSM はアルファベット 1 文字を 7bits (septet) で表す符号化方式です。User-Data が最長 140 オクテットなので、140 * 8 / 7 = 160 文字が送受信可能な最大文字数です。
利用可能な文字は英数字と一部の記号に限られています。[Wikipediaの表](https://en.wikipedia.org/wiki/GSM_03.38)が見やすいです。

最大 160 文字と書きましたが、Basic Character Set Extension に含まれる一部の記号（ `^` `|` `{` `}` `[` `]` `~` `\` など) はエスケープ文字（`0x1B`）とのペアで表現されるため、これらの文字は１文字で ２文字ぶん (2 septets = 14 bits) を消費します。つまり、使用する文字によっては最大文字数は 160 文字より少なくなることがあります。

### UCS-2

UCS-2 (Universal Character Set coded in 2 octets) は、1 文字を 16 bits で規定した符号化文字集合です。1 文字が 16 bit なので、140 * 8 / 16 = 70 文字が送受信可能な最大文字数になります。
日本語を含むメッセージを送受信したければ、こちらを選ぶしかありません。

実装上は、UCS-2 のコードポイントを利用した文字符号化方式である UTF-16 でエンコードされたデータが伝送されることが多いようです。Unicode の絵文字（😀😋など）は UCS-2 の範囲からは逸脱するのですが、スマートフォンなど多くの端末では UCS-2 を UTF-16 として扱うことで送受信できてしまいます。これら絵文字はサロゲートペアを使って表現されるので、1文字で 32 bits を消費します。また、端末によっては、UCS-2 の範囲外の文字は文字化けするなど意図しない挙動となることがあるかもしれません。

## 連結メッセージ

１通 70 文字だと短すぎるよ！という方のために、長いメッセージを複数のセグメントに分割して送信し、受信側で結合することで、ユーザからの見た目上は長文を送受信できるようにする仕組みがあります。連結メッセージ（Concatenated message）といいます。

分割したセグメントを再結合するために必要な情報を User-Data-Header (UDH) に格納して配置するため、1 つのセグメントあたりで送受信可能な文字数は減少します。
例えば UDH に 6 オクテットを使用し、メッセージを最大 7 通のセグメントに分割して送信する場合、UCS-2 で送信できる文字数は最大 (140 - 6) * 8 / 16 * 7 = 469 文字（GSM-7 では 1071 文字）ということになります。SORACOM Onyx LTE USB ドングルの場合は最大 7 通までの分割への対応のため、これが上限です。

分割可能なセグメントの最大数はデバイスの機種および利用する通信事業者に依存します。3GPP の仕様上はセグメントを区別する番号が 1からインクリメントされる 1 オクテットの値のため、最大で 255 個のセグメントに分割できることになりますが、実装上の制約のために実際に利用できるのは最大でも 10 セグメント程度が一般的なようです。最大 10 セグメントの場合、UCS-2 で 670 文字、GSM-7 で 1530 文字が上限です。

携帯電話各社の SMS 料金（例: [KDDI(au)の場合](https://www.au.com/mobile/service/sms/)）を見ると SMS の送信文字数に応じて細かく料金が区切られていますが、これは文字数によって内部的には複数通の SMS として料金が計算されるためと推察されます。


:::note info
連結メッセージの送信方法については、別記事『[Quectel BG96 で AT コマンドを使って 70 文字超の SMS を送信する](https://qiita.com/shozoo/items/e8e11d5144c1e4ef3251)』に書きました。
:::

# デバイスから SMS を送信する

前置きが長くなりました。いよいよ SMS を送信してみましょう。

送信先には、SORACOM Harvest Data (`901031`) の番号を指定して、SORACOM Harvest Data 上で SMS の到達を確認します。事前に、SIM グループ設定で [SORACOM Harvest Data を有効に](https://users.soracom.io/ja-jp/docs/harvest/enable-data/)しておきます。

:::note warn
SORACOM Air for セルラーの SIM の中で、SORACOM Harvest Data が利用できるのは、公式ドキュメントによる解説で『[グループA: SORACOM プラットフォームと SMS を送受信できる](https://users.soracom.io/ja-jp/docs/air/send-sms/) 』に分類されているサブスクリプションに限定されます。
SORACOM Harvest Data が利用できない SIM で試す場合は、宛先を別のデバイスなどの電話番号(MSISDN)に読み替えてください。
:::


## ネットワークへの接続状態を確認する

デバイスは既にネットワークに接続されており、ATコマンドを利用できる状態になっているものとします。ネットワークへの接続状態は `AT+CREG?` コマンドなどで確認すると良いでしょう。

```
AT+CREG?
+CREG: 0, 5
OK
```

SORACOM Onyx LTE USB ドングルの場合、戻り値の2つ目の数字は以下を表します。

* \<stat\>
   * 0: 未接続. 接続先のサーチも行っていない
   * 1: 接続済. ホーム網
   * 2: 未接続. 接続先のサーチ中または接続試行中
   * 3: 接続拒否
   * 4: 不明
   * 5: 接続済. ローミング網

つまり、`1` または `5` であれば良いことになります。SORACOM Air for セルラーの plan01s などの IoT SIMの場合、端末からはローミング扱いなので `5` になります。

## デバイスから GSM 7bit で SMS を送信する

SORACOM 公式ドキュメント内の [SORACOM Harvest Data にデータを送信する](https://users.soracom.io/ja-jp/docs/air/send-sms/#soracom-harvest-data-%e3%81%ab%e3%83%87%e3%83%bc%e3%82%bf%e3%82%92%e9%80%81%e4%bf%a1%e3%81%99%e3%82%8b) に実に簡潔でわかりやすい説明が書いてあるので、これに倣います。

> テキストを送信するモードに変更し、USB ドングルにテキストを渡す際の文字エンコーディングを IRA に変更して、宛先に Harvest Data の MSISDN (`901031`) を指定します。
```
AT+CMGF=1
OK

AT+CSMP=17,167,0,0
OK

AT+CSCS="IRA"
OK

AT+CMGS="901031"
>
```

> 送信するテキストを入力して、Ctrl + Z キーを押します。
```
{"temperature": 20}     (←送信するテキストを入力して、Ctrl + Z キーを押します)
```

> SMSが送信されます。
```
+CMGS: 13

OK
```

さてどうなったか、SORACOM ユーザーコンソールにログインして、データを確認してみましょう。

![Screenshot 2023-12-12 at 23.06.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960954/a41725c1-ffb0-87a9-e26a-c0e2a8e9d7dd.png)

やったね！無事に保存されていました。

## デバイスから UCS-2 で SMS を送信する

UCS-2でも同様にやってみましょう。解説は後回しにします。

```
AT+CMGF=1
OK

AT+CSMP=17,167,0,8
OK

AT+CMGS="901031"
>
```

今度は送信するテキスト本文については UTF-16 エンコードされたデータを16進数で表すHex文字で入力します。なんじゃこりゃあって感じですが、お付き合いください。
入力が完了したら、Ctrl + Z キーで送信する点は同様です。
```
007b00226c1752060022003a0022d83edd230022007d　(最後に Ctrl + Z)
```

さてどうなったでしょうか？

![Screenshot 2023-12-12 at 23.21.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960954/766d7f6e-6e96-3aa1-7fef-7c02ccda9c2c.png)

やりました！さすがにグラフ用データとしての処理は無理があったようですが、テキストはきちんと保存されています。UTF-16 での絵文字も問題なく送れるようです。

なお、Harvest Data 上では UTF-8 に変換されて保存されている模様で、SORACOM API で取得した結果は UTF-8 でエンコードされていました。アプリケーションとしてはその方が扱いやすいので、ありがたいですね。

## SMS 送信関連の AT コマンド

それでは後回しにしたコマンドの意味の解説です。SORACOM Onyx LTE USB ドングルに内蔵されているモジュールである Quectel EG25-G の [AT Commmands Manual](https://www.quectel.com/product/lte-eg25-g) を元にしています。
今回は [3GPP TS 27.007](https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=1515) などの標準仕様で規定されているコマンドだけを使用していますが、デバイスの機種によって動作が異なる可能性はあります、もし試される場合は使用されるデバイスの説明書をできる限り参照してください。

### "デバイス"の構成

ところで、ここまでセルラー端末のことをふわっと "デバイス" と表現していましたが、ドキュメントで使用される用語の理解をしておかないと説明書を読んだ時に混乱してしまうので、ここで紹介しておきます。

![Screenshot 2023-12-13 at 1.14.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960954/f7a4175a-ac31-816a-1368-f99b7ff95559.png)

ユーザ端末（User Equipment）は、TE、MT、USIM の３つで構成されます。USIM はいわゆる SIM カード、MT は Onyx LTE USB ドングルのようなセルラーモデムデバイス、TE はモデムを利用する PC や Raspberry Pi などのホストです。MT（USBドングル）側で、TE (ホスト) との接続インターフェースを終端する部分を TA (Terminal Adapter) といいます。

Onyx LTE USB ドングルの例でいえば TE - MT (TA) 間は USB 2.0 インターフェースで接続されており、AT コマンドはその上の仮想シリアルインターフェースを通じてやり取りされていることになります。

### AT+CMGF: Message Format
TE - MT 間（ホストとドングルとの間）で送受信 SMS メッセージを受け渡しする際のフォーマットを指定するコマンドです。

```
AT+CMGF[=<mode>]
```

* \<mode\>
  * 0: PDU mode
  * 1: Text mode

0 (PDU mode) はこの記事の冒頭に示したような SMSメッセージの TPDU 全体を受け渡しする豪快なモードです。多分アプリケーションの方でヘッダ部分まで含めて解釈/生成したい場合に使用するのだと思います。試してみるには、[Online SMS PDU encoder](https://www.smsdeliverer.com/online-sms-pdu-encoder.aspx) などを使うと良いかもしれません。

1 (Text mode) に設定すると、先の例で示したように、ヘッダのパラメータと本文テキストを個別の AT コマンドで設定できるようになります。

### AT+CSMP: Set SMS Text Mode Parameters

`AT+CMGF=1` で Text mode を設定した際に、TPDU の構築に用いられるパラメータを指定するコマンドです。SMS をネットワーク上で交換する際のメッセージ本文の符号化方式（Data-Coding-Scheme) はこれを使って指定します。

```
AT+CSMP=<fo>[,<vp>[,<pid>[,<dcs>]]]
```

* \<fo\>
    * TPDU の先頭1オクテットの値を10進数で指定します
    * `17`(=`0b00010001`) を指定すると、TP-MTI (Message Type Indicator) が `01` (SMS-SUBMIT)、TP-VPF (Validity Period Format) が `01` (relative format) という指定になります
* \<vp\>
    * メッセージの有効期間 (Validity period) を指定します
    * TP-VPF が relative format の場合、`167` は以下の意味になります
        * (12 hours + (167 - 143) x 30 minutes) = 24 hours
        * ※ 上記の計算式自体、数値の範囲によって段階的に変化します
* \<pid\>
    * TP-Protocol-Identifier の値を指定します。デフォルト `0` です
* \<dcs\> 
    * Data Coding Scheme を指定します。
    * `0` で GSM、`8` でUCS-2 の指定になります

### AT+CSCS: SMS Character Sets Conversions

TE と TA との間で使用する文字セットを指定するコマンドです。

```
AT+CSCS[=<chset>]
```
* \<chset\>
    * `"GSM"`: GSM 7bit default alphabet (3GPP TS 23.038)
    * `"IRA"`: International reference alphabet (ITU-T Rec. T.50)
    * `"UCS2"`: 16-bit universal multi-octed coded character set (ISO/IEC 10646)

Data Coding Scheme (DCS) と混同しそうになるのですが、DCS はネットワーク上でメッセージが伝送される際の符号化方式（GSM or UCS2)であり、こちらは TE と TA の間の(仮想)シリアルインターフェース上だけに作用する設定です。

DCS にGSMを使用する際には、こちらのchsetも `"GSM"` を使えば良さそうに思えるのですが、これは色々問題がありお勧めできません。このモードでは、シリアルインターフェースを通って入力された文字がそのまま GSM-7bitとして解釈されます。一方で、通常のホスト(TE)では文字コードに ASCII や UTF-8 が設定されているかと思います。そうすると ASCII として入力された文字が GSM-7bit として解釈されることによって、コードポイントの異なる文字が文字化けしてしまいます。
さらに、もしシリアルインターフェースでソフトウェアフロー制御が有効になっていると容易に問題が起きる、という注意書きが 3GPP TS 27.007 に記載されています。これは、GSM 7bit においては通常の文字である　`_` / `Γ`が `0x11` / `0x13` で定義されており、ASCII文字における XON / XOFF に一致してしまうことによるものと思われます。

一方、IRA で定義されているコードポイントは ASCII と一致する（ASCIIはIRAで定義されていない文字を含みますが）ので、GSM 7bit を使って英数字を送受信する際には `"IRA"` を設定しておけばほぼ問題は起きないということになります。ASCII で定義されているが GSM 7 bit に含まれない文字を入力した際の動作は端末依存ですが、SORACOM Onyx LTE USB ドングルの場合は空白文字に変換されるようです。

`"UCS2"` を設定した際は、1文字あたり 16 bits の UCS2 文字を `0000` から `FFFF` の16進数(hex)文字で入力します。例えば `SORACOM` という7文字の文字列は、`0053004f005200410043004f004d` (28文字) になります。

なお、SORACOM Onyx LTE USB ドングルの場合、`AT+CSCS=` の設定が作用するのは DCS が GSM 7 bit に設定されている場合だけで、DCS が UCS2 の場合は `AT+CSCS` の設定に関わらず `"UCS2"` の動作（hex文字で入力) になるようです。

SORACOM Onyx LTE USB ドングルでは対応していないのですが、3GPP TS 27.007 では \<chset\> に `"UTF-8"` というものも規定しています。こちらに対応したデバイスであれば、人間にとってわかりにくい hex 文字ではなく、UTF-8 でエンコードされた文字で直接入出力ができるようです。

### AT+CMGS: Send Message

TE（ホスト）からネットワークにあてて SMS を送信するコマンドです。なお、ここでは Text mode (`AT+CMGF=1`) に関してのみ解説します。 

```
AT+CMGS=<da>[,<toda>]<CR>
text is entered
<Ctrl+Z/ESC>
```

* \<da\>
    * メッセージ送信先のアドレス (Destination address)を指定します
        * MSISDN、いわゆる電話番号です
        * ハイフンやスペースは含めず、`"` で括って文字列として指定します
        * 送信先が国際電話番号の場合は先頭の `+` を含めます（`"+8190... "` など)
* \<toda\>
    * メッセージ送信先のアドレスの種別を 10 進数値で指定します。省略可能です
    * 送信メッセージ TPDU のうち、宛先のアドレス（TP-Destination-Address)に含まれる Type-Of-Address とよばれる 1 オクテットに設定する値を 10 進数で指定するものです

\<toda\> を指定する場合、通常の用途では `129` か `145` を指定します。

* `129` (0x81): Unknown (0) - ISDN (1)
    * 送信先が `"090... "` などの国内電話番号や、SORACOM Harvest Data などの SORACOM のエンドポイントの場合に指定します
* `145` (0x91): International Number (1) - ISDN (1) 
    * 送信先が `"+81... "` などの国際電話番号や、SORACOM IoT SIM の[グループ A](https://users.soracom.io/ja-jp/docs/air/send-sms/) の MSISDN の場合に指定します

<CR> (改行文字) の後ろには本文として送信したいテキストを入力し、最後に Ctrl+Z を送信することでメッセージが送信されます。

# おわりに

本記事では、デバイスから AT コマンドを使って SORACOM に SMS を送信する方法について紹介しました。宛先を別のデバイスの MSISDN に変更すれば、デバイス→デバイスの送信にも応用できるはずです。本当は受信の方法も書こうと思っていたのですが、送信だけでボリュームが多くなり、筆者がここで力尽きてしまいました。

冒頭で述べたように、SMS はデータ量あたりの料金でいえば IP パケット通信よりも割高なのですが、状況によってはまだ使い道のあるサービスです。

 * デバイスからデバイス宛の通信ができます
 * ネットワーク（SORACOM APIなど）からデバイス宛の下りの通信ができます
 * TCP/IP のプロトコルスタックを持っていないデバイスでも、ATコマンドだけで利用できます
 * 不到達時にも、ネットワーク側である程度リトライを行ってくれます（到達保証はありませんが）

 本記事が、IoT デバイスでの SMS の利用を検討されている方の一助になりましたら、あるいはせめて技術的暇つぶしのネタにでもなりましたら幸いです！