はじめに
『MSX0 Stack』付属の IoT BASIC サンプルを『Turbo Pascal』へ移植してみます。
SEND2NET.BAS
◆サーバーとの送受信
項目 | 説明 |
---|---|
概要 | 温度湿度センサーから値を取得しサーバーへ送信します。 |
ファイル名 | SEND2NET.BAS |
対応デバイス | DHT11 (水色) DHT20 (黒色) |
コメント | IoT データの可視化サービスを行われているアンビエントデーター株式会社様のサーバーにセンサーから取得した値を送信します。送信したデータはサーバー側でグラフ化され、PCやスマートフォンのブラウザから閲覧できます。Ambient サービスはとてもお手軽に IoT を実現できるため、こちらのサンプルコードを用意させていただきました。 |
このサンプルを動作させるには外部センサーが必要です。
See also:
Ambient
Ambient は IoT データの可視化サービスです。マイコンなどから送られるセンサデータを受信し、蓄積し、可視化(グラフ化)します。
事前にユーザー登録 (無料) が必要です。サイト右上のボタン (ハンバーガーアイコンの中にあるかもしれません) からユーザー登録を行います。
ユーザー登録を行うと登録したメールアドレスに 「Ambient」新規登録確認メール
というメールが送られてきますので、メールに書かれている URL をクリックしてユーザー登録を完了させます。
メールアドレスによっては新規登録確認メールが送られてこない事があるようです。そのような場合には GMail 等でメールアカウントを作成しましょう。
ユーザー登録が終わってログインすると [チャネル一覧] からチャネルを作成できるようになります。
MSX0 Stack での挙動
まず事前にセンサーをつないでおきます。
右中央のセンサーが温湿度センサーなのですが、 『Grove Beginner Kit for Arduino』の製造ロットによって、センサーの種類と MSX0 への接続先が異なります。具体的には 2022/04 以降製造分に DHT20 が付属するようです。
センサー | 接続方法 | Grove ポート |
---|---|---|
[D3] DHT11 (水色) | Digital | Port C (水色) |
[I2C] DHT20 (黒) | I2C | Port A (赤) |
『Grove Beginner Kit for MSX0』には DHT20 が付属します。
SEND2NET.BAS
をロードして、
LIST 140-180
を実行します。xxx となっている所に Ambient のチャネル情報を入力します。
行 | 変数 | 説明 |
---|---|---|
140 | CH$ | Ambient のチャネル ID |
150 | WK$ | Ambient のライトキー |
170 | WT | 待ち時間 (秒) |
180 | RB | リブートフラグ |
とりあえず 140 行目と 150 行目を書き替えて実行します。
30 秒おき (変数 WT で変更可能) に Ambient へデータが送られます。データは Ambient のチャネル情報 で確認できます。
変数 RB の値がデフォルトの 0 の場合には、BASIC プログラムのループ内で定期的にデータが送られますが、変数 RB の値が 1 の場合には、データが送られると MSX0 Stack が再起動します。つまりウォッチドッグタイマー的な動作をします。
BASIC プログラムを書き替えたら、
SAVE "AUTOEXEC.BAS"
として保存すると、MSX0 Stack 起動時に AUTOEXEC.BAS
が読み込まれ、データを Ambient へ送信して再起動...を繰り返します。
プログラムは〔Ctrl〕+〔Stop〕(リモートコントロールパネルからは〔Ctrl〕+〔F12〕)で中断できます。ファイル削除コマンドは KILL です。
See also:
Turbo Pascal へ移植
別途、MDL-LIB と SYSUTILS.LIB
、IOT.LIB
が必要です。
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 | リブートフラグ |
実行してみました。
センサーが正しく接続されていれば Ambient へデータが送られます。
RB を 1 に設定する場合 (ウォッチドッグタイマー的に使う) には実行形式ファイル (*.COM
) を生成し、AUTOEXEC.BAT
(または REBOOT.BAT
) で自動実行されるようにしてください。
任意のキーで中断できます。
解説
IoT ルーチン
MSX BASIC の文字列は最大 255 文字で、IoT BASIC の送信文字列もその影響を受けるのですが、内部では 64 byte 区切りで送受信してるようです。Turbo Pascal の IOT.LIB はこれを考慮していません。つまり、自分で 64 byte 以下に区切って送信する必要があります。
IOT.LIB を修正するかは未定です。結局、255 文字を超える文字列をやりとりする際には BASIC でも分割処理を行う必要があるので…
配列
Content-Length
の値を算出するために、配列への代入の順序を変更しています。
TIME システム変数 (BASIC)
BASIC の TIME システム変数は ワークエリア FC9E (JIFFY) と同じです。MDLLIB.LIB
では sys_timer
として定義されています。
See also:
おわりに
文字列分割の所が少し厄介でしたね。コードも少し長くなってしまいました。
記事に関連しますが、以前 MSX-DOS で電源断または再起動を行うトランジェントコマンド M0SHTDN を作った事があります。