IoTの全体像の例「照度センサーの情報を表示する」
http://ameblo.jp/smeokano/entry-12240624461.html
の続きです。上記のエントリーで
「それぞれの詳しいプログラムは、後日、この下に掲示します。」
とありますが、その「詳しいプログラム」第二弾、ゲートウェイ(GW)部分です。
●ゲートウェイ部とは
ゲートウェイ部は
・各センサーとRS485で通信し、データを取得し
・その結果をサーバー部へHTTPプロトコルで通信し、設定します
そのため、以下の機能をもっています。
(1)各センサーに「データ送信依頼」を送信する
ここでは、以下のフォーマットになっています
0x02 ID(4桁の数字) 0x03
合計6バイト
(2)センサーから送られてきたデータを受信する
受信データは、以下のフォーマットになっています
0x02 値(4桁の数字) 0x03
合計6バイト
(3)受信したデータをサーバー部へHTTP経由で送信する
送信先URL:http://127.0.0.1:8080/setdata (GET型)
引数 data センサーから受け取った値そのまま
●ゲートウェイ部概要
今回は、id1~9999までに対し、上記(1)~(3)を行うプログラム
をC言語で作成しました(動作プラットフォームはRaspberry PI2)。
1回分なので、1時間おきに行うような場合は、cronを使ってまわすなり、
sleepしておいて、定期的に回るようにします。
また、「IoTの詳細(1)」でつくったセンサー部とつなげられますが、
それは、IDを見ていないので、IDが何でも返ってきてしまいます
特定のIDに対して返したい場合は、センサー部プログラムにその部分を
追加してください。
以下、プログラムについてRS485の部分、HTTPの部分に分けて述べます
●RS485の部分のプログラム
「ゲートウェイ部とは」(1)(2)の部分は、RS485通信をします
Raspberry PI2でRS485を行うには、以下の2つのことをする必要があります
(あ)GPIOのどこかのピンをDE/RE制御用と決め、そのピンに対して
送信のとき1、受信のときに0を送る
(い)/dev/ttyAMA0に対して、送信の時には書き込み、受信の時には
読み込みをします。
ただし、(い)の送受信を行うときに、バッファリングをされるとこまるので、
非カノニカルモードにします
非カノニカルモードにすると、送信動作後、すぐに返ってきます。そこで、
writeした後、すぐに(あ)の受信モードにしてしまうと、送信が終わりません
送信動作終了がとれないため、送信動作が終了するであろう時間分
待っています。それが、1.2msの待ちです。
また、受信は、何時データが来るかわからないので、selectで取ります
今回は50ミリ秒待ってとれなかったら、タイムアウトにしています。
→プログラムでは「データ転送」として、ファイルディスクリプタdfdを使っています
(あ)の部分についてですが、GPIOの物理ピン番号18のピン(TX,RXの隣のピン)
を使っています(gpio readall したとき GPIO .5 と書いてあるピン)。
このピンを使ってGPIOをON/OFFするには、
・/sys/class/gpio/exportに18と書き込んで、18番を使う準備をさせる
・/sys/class/gpio/gpio18/directionにOUTとかきこんで、出力端子にする
・/sys/class/gpio/gpio18/valueをオープンする
(それぞれ、100msの時間待ちをしています。そんなに待たなくていいかも?)
を行って準備した後、/sys/class/gpio/gpio18/valueに対して
・送信のときは1を送信する前に書き込みます
・送信終了したら、受信するために、即座に0を書き込みます
使い終わったらクローズします。
→プログラムでは「制御信号」として、ファイルディスクリプタefdを使っています
なお、DEとREの線は別々の線(2本必要?)ですが、半二重の場合、共用しても
問題ありません。
問題は、送信から受信の切り替え時です。
送信終了後、すぐに受信に切り替えないと、センサー部から送られてきた通信を
取りこぼします。
そこで、送信分だけSleepしたら、すぐに受信に切り替えています。
もし、これで間に合わないようだったら、センサー側で、送信まで少し待つ必要
があります(が、私のケースでは待たなくて大丈夫でした)
●HTTP部分のプログラム
まとめて、RS485通信が終わった後に書いています。以下のことをします
・送信先アドレスとポート番号を設定する
・IPv4 TCP のソケットを作成する
・サーバ接続
・送信データ作成
・送信
・クローズ
このあと、500ミリ秒待っています
サーバー側にデータが溜まりすぎないように、適当な流量にするための待ちで、
こんなに待たなくても良いかと思いますが。。。
なお、「送信先アドレスとポート番号を設定する」は1回やればいいので、
別にここでやらずに、はじめのほうでもいいのですが、見易さのため、
ここに書いてやっています(プログラムの効率的には良くないけど)
●実際のプログラム
こんなかんじ。
/*
* GWサンプルプログラム
* 内容 id1から9999までアクセス
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include <fcntl.h>
#include <time.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#define SERIAL_PORT "/dev/ttyAMA0"
#define CTRL_PORT "/sys/class/gpio/gpio18/value"
#define BAUDRATE B57600
// サーバーのIPアドレス
#define MYHOST "127.0.0.1"
//==============================================//
// //
// メインプログラム //
// //
//==============================================//
int main(int argc, char *argv[])
{
//==== RS485で利用 ====
int dfd,efd; // dfd :データ efd:RE/DE制御
char com[16]; // RS485用電文バッファ
struct termios newtio, oldtio; // コントロールの構造体
int lp; // idがセットされている
struct timespec ts; // 待ち時間セット構造体
int sts; // 1:受信中(0x02が来て03未着)
fd_set fds; // selectの構造体
struct timeval timeout; // select待ち時間構造体
//==== インターネットで利用 ====
int sd;
char buf[64]; // インターネット用電文バッファ
struct sockaddr_in addr;
struct hostent *host;
int result; // 戻り値
//==============================//
// データ転送オープン //
//==============================//
if(!(dfd = open(SERIAL_PORT, O_RDWR)))
{
printf("data error \n");
return -1;
}
// 現在のシリアルポートの設定を待避
ioctl(dfd, TCGETS, &oldtio);
// ポートのコントロール初期化
bzero(&newtio, sizeof(newtio));
newtio = oldtio; // ポートの設定をコピー=基本的には同じにする
// ボーレートなど設定
newtio.c_cflag = (BAUDRATE | CS8 | CLOCAL | CREAD);
// 非カノニカルモード
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 初期設定
ioctl(dfd, TCSETS, &newtio);
//==============================//
// 制御信号オープン //
//==============================//
//==== GPIO18の使用開始を指定する ====
efd = open("/sys/class/gpio/export", O_WRONLY);
if (efd < 0)
{
printf("GPIO export open error.\n");
exit(1);
}
write(efd,"18",3);
close(efd);
// 切り替え時間待ち
ts.tv_sec = 0;
ts.tv_nsec = 100*1000*1000; // 100ms
nanosleep(&ts, NULL);
//==== GPIO18を出力に指定する ======
efd = open("/sys/class/gpio/gpio18/direction", O_WRONLY);
if (efd < 0)
{
printf("GPIO direction open error.\n");
exit(1);
}
write(efd,"out",4);
close(efd);
// 切り替え時間待ち
ts.tv_sec = 0;
ts.tv_nsec = 100*1000*1000; // 100ms
nanosleep(&ts, NULL);
//==== 制御信号オープン ==============
if(!(efd = open(CTRL_PORT, O_WRONLY)))
{
printf("ctrl error \n");
return -1;
}
//==============================//
// データ処理 //
//==============================//
lp = 1; // はじめのid
while(lp < 10000) // id=9999までチェック
{
// DE/RE コントロール送信
strcpy(com,"1");
write(efd,com,2);
// 電文作成
bzero(com, sizeof(com));
sprintf(com,"%c%04d%c",0x2,lp,0x3);
// 電文書き出し
write(dfd,com,6); // len = 6
// 送信終了待ち
ts.tv_sec = 0;
ts.tv_nsec = 1200*1000; // 1.2ms=1 word time(0.2ms?) * len
nanosleep(&ts, NULL);
// DE/RE 切り替え=コントロール送信
strcpy(com,"0");
write(efd,com,2);
// 受信
// データ領域等クリア
bzero(com, sizeof(com));
sts = 0;
// select用設定
FD_ZERO(&fds);
FD_SET(dfd, &fds);
timeout.tv_sec = 0;
timeout.tv_usec = 50*1000;
//==============================//
// 1電文受信 //
//==============================//
while(1)
{
//==============================//
// データ来るまで待つ //
//==============================//
if (select(dfd+1, &fds, NULL, NULL, &timeout) <= 0)
{
//================================
// 来なかった(timeout)
//================================
printf("no data id=%d errno=%d \r\n",lp,errno);
break;
}
else
{
//================================
// 来た→1バイトづつ読み込み
//================================
// 読み込み
if ( read(dfd,com,1) <= 0 )
{
printf("error \r\n");
continue;
}
// 内容チェック
switch(com[0])
{
case 0x02: // 開始文字
sts = 1;
bzero(buf, sizeof(buf));
break;
case 0x03: // 終了文字
sts = 0;
break;
default: // その他
if ( ( sts == 1 ) && ( com[0] > ' ') )
{
// 内部で文字なら格納
com[1] = 0;
strcat(buf,com);
}
break;
}
//=======================================
// ここでは、デバッグ用に文字出力
// 本来は不要なもの
//=======================================
if ( com[0] == 0x03 )
{ // 終了文字
printf(" e%x \r\n",com[0]);
break;
}
if ( com[0] >= ' ')
{ // 表示して見えるとき
printf("%c",com[0]);
}
else
{ // 表示して見えないとき
printf(" e%x ",com[0]);
}
}
}
//===== サーバーに送る必要あるかチェック ====
if(strlen(com) <= 0 )
{
// 待ち
ts.tv_sec = 0;
ts.tv_nsec = 500*1000*1000; // 500 ms
nanosleep(&ts, NULL);
// 次のIDへ
lp++;
continue;
}
//========================================//
// サーバーにデータ送信
//========================================//
// TCP/IPの初期化:送信先アドレスとポート番号を設定する
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = inet_addr(MYHOST);
// IPv4 TCP のソケットを作成する
if((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket error\n");
return -1;
}
// サーバ接続
result=connect(sd, (struct sockaddr *)&addr,
sizeof(struct sockaddr_in));
if(result < 0){
printf("connection error\n");
}
// 送信データ作成
strcpy(com,buf);
sprintf(buf,"GET /setdata?data=%s HTTP/1.0\n\n",com);
//==== デバッグ用:本来いらない
printf("send:%s",buf);
// パケットを TCP で送信
if(write(sd, buf, strlen(buf)) < 0)
{
printf("send error\n");
return -1;
}
//==== デバッグ用:本来いらない
// 結果のはじめ20バイト読み込み
read(sd,buf,32);
buf[12]=0; // NULLでおわらす
printf("result:%s\n",buf);
// クローズ
close(sd);
// 待ちと、次のIDへ
ts.tv_sec = 0;
ts.tv_nsec = 500*1000*1000; // 500 ms
nanosleep(&ts, NULL);
lp++;
}
//==============================//
// データ転送終了 //
//==============================//
close(efd);
newtio = oldtio; // ポートの設定を戻す
ioctl(dfd, TCSETS, &newtio); // 前に戻す
close(dfd);
}
【参考サイト】
http://www.rt-shop.jp/blog/archives/4145 (シリアル通信)
http://independence-sys.net/main/?p=2181 (GPIO)
http://simd.jugem.jp/?eid=145 (時間部分)