【MSX0】SEND2NET.PAS【Turbo Pascal】

# はじめに
『MSX0 Stack』付属の IoT BASIC サンプルを『Turbo Pascal』へ移植してみます。

 - [【MSX0】IoT BASIC サンプルの移植【Turbo Pascal】(Qiita)](https://qiita.com/ht_deko/items/2362488c5c99aec4afbf)

# SEND2NET.BAS
◆サーバーとの送受信

| 項目 | 説明 |
|:---|:---|
| 概要 | 温度湿度センサーから値を取得しサーバーへ送信します。 |
| ファイル名 | SEND2NET.BAS |
| 対応デバイス | DHT11 (水色)<br>DHT20 (黒色)|
| コメント | IoT データの可視化サービスを行われているアンビエントデーター株式会社様のサーバーにセンサーから取得した値を送信します。送信したデータはサーバー側でグラフ化され、PCやスマートフォンのブラウザから閲覧できます。Ambient サービスはとてもお手軽に IoT を実現できるため、こちらのサンプルコードを用意させていただきました。 |

:::note warn
このサンプルを動作させるには外部センサーが必要です。
:::

**See also:**

 - [『MSX0 Stack』の操作方法 - ハードウェア (Qiita)](https://qiita.com/ht_deko/items/cdb1594e9879a5e9e660)
 - [Grove Beginner Kit for Arduino (Switch Science)](https://www.switch-science.com/products/6361)

## Ambient 
Ambient は IoT データの可視化サービスです。マイコンなどから送られるセンサデータを受信し、蓄積し、可視化(グラフ化)します。

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/bd74bfb5-a16a-2750-9e90-d2a2aa1bc1a3.png)](https://ambidata.io/)

 - [Ambient (Ambient)](https://ambidata.io/)

事前にユーザー登録 (無料) が必要です。サイト右上のボタン (ハンバーガーアイコンの中にあるかもしれません) からユーザー登録を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/e05f1421-ef7c-4822-d573-78bf6f046d02.png)

ユーザー登録を行うと登録したメールアドレスに `「Ambient」新規登録確認メール
` というメールが送られてきますので、メールに書かれている URL をクリックしてユーザー登録を完了させます。

:::note info
メールアドレスによっては新規登録確認メールが送られてこない事があるようです。そのような場合には GMail 等でメールアカウントを作成しましょう。
:::

ユーザー登録が終わってログインすると [[チャネル一覧]](https://ambidata.io/ch/channels.html) からチャネルを作成できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/91ba0cd1-1836-e622-478f-7224c7b7b103.png)


## MSX0 Stack での挙動

まず事前にセンサーをつないでおきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/7422a9be-8cd6-3dd3-cef9-2ecaa4e817b1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/62f448b7-f814-57be-061a-9a9014eab192.png)

右中央のセンサーが温湿度センサーなのですが、 [『Grove Beginner Kit for Arduino』](https://www.seeedstudio.com/Grove-Beginner-Kit-for-Arduino-p-4549.html)の製造ロットによって、センサーの種類と MSX0 への接続先が異なります。具体的には 2022/04 以降製造分に DHT20 が付属するようです。

| センサー | 接続方法 | Grove ポート |
|:---|:---|:---|
| [D3] DHT11 (水色) | Digital | Port C (水色) |
| [I<sup>2</sup>C] DHT20 (黒)| I<SUP>2</SUP>C  | Port A (赤) |

:::note
『Grove Beginner Kit for MSX0』には DHT20 が付属します。
:::


`SEND2NET.BAS` をロードして、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/5966c94c-8dab-0d94-8ce6-8a8aa0d0f66f.png)

`LIST 140-180` を実行します。xxx となっている所に Ambient のチャネル情報を入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/4278e6ef-247b-6942-3b05-1a1823c7fc56.png)

| 行 | 変数 | 説明 |
|:---|:---|:---|
| 140 | CH$ | Ambient のチャネル ID |
| 150 | WK$ | Ambient のライトキー |
| 170 | WT | 待ち時間 (秒) |
| 180 | RB | リブートフラグ |

とりあえず 140 行目と 150 行目を書き替えて実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/2d348455-c9c6-7bfb-0562-9bb7b0ce377a.png)

30 秒おき (変数 WT で変更可能) に Ambient へデータが送られます。データは [Ambient のチャネル情報](https://ambidata.io/ch/channels.html) で確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/4c3a3ff4-1dac-5a8c-5081-964da399dede.png)

変数 RB の値がデフォルトの 0 の場合には、BASIC プログラムのループ内で定期的にデータが送られますが、変数 RB の値が 1 の場合には、データが送られると MSX0 Stack が再起動します。つまりウォッチドッグタイマー的な動作をします。

BASIC プログラムを書き替えたら、

```
SAVE "AUTOEXEC.BAS"
```

として保存すると、MSX0 Stack 起動時に `AUTOEXEC.BAS` が読み込まれ、データを Ambient へ送信して再起動...を繰り返します。

:::note info
プログラムは〔Ctrl〕+〔Stop〕(リモートコントロールパネルからは〔Ctrl〕+〔F12〕)で中断できます。ファイル削除コマンドは KILL です。
:::

**See also:**

 - [Grove Beginner Kit for Arduinoの温湿度センサーがアップデート (Seeed K.K. エンジニアブログ)](https://lab.seeed.co.jp/entry/2022/12/26/120000)
 - [ウォッチドッグタイマー (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%A9%E3%83%83%E3%83%81%E3%83%89%E3%83%83%E3%82%B0%E3%82%BF%E3%82%A4%E3%83%9E%E3%83%BC)

## Turbo Pascal へ移植

別途、MDL-LIB と `SYSUTILS.LIB`、`IOT.LIB` が必要です。

 - [MDL-LIB version 2.2 の使い方 (Qiita)](https://qiita.com/ht_deko/items/6f15aee0bd25b550e4df)
 - [【MSX0】IOT.LIB (Qiita)](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c)


```pascal:SEND2NET.PAS
program SEND2NET;
type
  IntStr = string[8];
{$I MDLLIB.LIB}
{$I SYSUTILS.LIB}
{$I IOT.LIB}
const
  HS = '54.65.206.59';
  CH = 'xxxxx';
  WK = 'xxxxxxxxxxxxxxxx';
  PA = 'msx/me/if/NET0/';
  WT = 30;
  RB = 0;
var
  Flg: Boolean;
  I, D1, D2, D3: Integer;
  SM: array [0..8] of LibStr;
  function ToString(v: Integer): IntStr;
  var
    s: IntStr;
  begin
    Str(v, s);
    ToString := s;
  end;
  function MLen(s, e: Byte): Byte;
  var
    i, v: Integer;
  begin
    v := 0;
    for i:=s to e do
      v := v + Length(SM[i]);
    MLen := v;
  end;
begin
  { user setting }
  if CH ='xxxxx' then
  begin
    Writeln('please change the value CH and WK to your enviroment.');
    Exit;
  end;
  Flg := RB = 1;
  repeat
    { connect }
    IoTPutStr(PA + 'conf/addr', HS);
    IoTPutInt(PA + 'conf/port', 80);
    IoTPutInt(PA + 'connect', 1);
    { check connect status }
    Delay(500);
    if IoTGetInt(PA + 'connect') <> 1 then
      { error }
      Writeln('connect fail')
    else
    begin
      { get sensor value }
      D1 := IoTGetInt('device/dht/temperature');
      D2 := IoTGetInt('device/dht/humidity');
      D3 := IoTGetInt('host/battery/level');
      { create message }
      SM[5] := '{"writeKey":"' + WK + '",';
      SM[6] := '"d1":"' + ToString(D1) + '",';
      SM[7] := '"d2":"' + ToString(D2) + '",';
      SM[8] := '"d3":"' + ToString(D3) + '"}'#13#10;
      SM[0] := 'POST /api/v2/channels/' + CH + '/data HTTP/1.1'#13#10;
      SM[1] := 'Host: ' + HS + #13#10;
      SM[2] := 'Content-Length: ' + ToString(MLen(5, 8)) + #13#10;
      SM[3] := 'Content-Type: application/json'#13#10;
      SM[4] := #13#10;
      { send message }
      Writeln(#13#10'---- Send Message ----');
      for I:=0 to 8 do
      begin
        Write(SM[I]);
        IoTPutStr(PA + 'msg', SM[I]);
      end;
      Delay(500);
      { receive message }
      Writeln(#13#10'---- Receive Message ----');
      for I:=0 to 10 do
      begin
        Write(IoTGetStr(PA + 'msg'));
        Delay(100);
      end;
    end;
    { disconnect }
    IoTPutInt(PA + 'connect', 0);
    { loop }
    if RB = 0 then
    begin
      { wait }
      Writeln(#13#10'---- Wait (', WT, ' sec ) ----');
      sys_timer := 0;
      while (sys_timer < WT * 60) and (not Flg) do
        Flg := Flg or KeyPressed;
    end
    else
    begin
      { system reboot }
      Writeln(#13#10'---- Sleep (', WT, ' sec ) ----');
      IoTPutInt('host/power/wait', WT);
      IoTPutInt('host/power/reboot', 1);
    end;
  until Flg;
end.
```

定数を環境に合わせて書き替えてください。

| 定数 | 説明 |
|:---|:---|
| CH | Ambient のチャネル ID |
| WK | Ambient のライトキー |
| WT | 待ち時間 (秒) |
| RB | リブートフラグ |


実行してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3cd1017e-fe02-675b-2887-23205caa0216.png)

センサーが正しく接続されていれば Ambient へデータが送られます。

RB を 1 に設定する場合 (ウォッチドッグタイマー的に使う) には実行形式ファイル (`*.COM`) を生成し、`AUTOEXEC.BAT` (または `REBOOT.BAT`) で自動実行されるようにしてください。

:::note info
任意のキーで中断できます。
:::

### 解説

#### IoT ルーチン
MSX BASIC の文字列は最大 255 文字で、IoT BASIC の送信文字列もその影響を受けるのですが、内部では 64 byte 区切りで送受信してるようです。Turbo Pascal の IOT.LIB はこれを考慮していません。つまり、自分で 64 byte 以下に区切って送信する必要があります。

:::note
IOT.LIB を修正するかは未定です。結局、255 文字を超える文字列をやりとりする際には BASIC でも分割処理を行う必要があるので…
:::

#### 配列
`Content-Length` の値を算出するために、配列への代入の順序を変更しています。

#### TIME システム変数 (BASIC)
BASIC の TIME システム変数は ワークエリア FC9E (JIFFY) と同じです。`MDLLIB.LIB` では `sys_timer` として定義されています。 

**See also:**

 - [Appendix A.2 ワークエリア (MSX Datapack wiki化計画)](http://ngs.no.coocan.jp/doc/wiki.cgi/datapack?page=Appendix+A%2E2+%A5%EF%A1%BC%A5%AF%A5%A8%A5%EA%A5%A2)

# おわりに
文字列分割の所が少し厄介でしたね。コードも少し長くなってしまいました。

記事に関連しますが、以前 MSX-DOS で電源断または再起動を行うトランジェントコマンド **M0SHTDN** を作った事があります。

 - [M0SHTDN -【MSX0】IOT.LIB (Qiita)](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c#-m0shtdn)