Quantcast
Channel: コンピューターの勉強会・研修ネタ
Viewing all articles
Browse latest Browse all 66

IoTの詳細(2) GW部(RS485⇔HTTP変換)

$
0
0

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 (時間部分)


Viewing all articles
Browse latest Browse all 66

Trending Articles