[天気予報アナウンスシステム]
system
 
[構成]
connect
Raspberry Pi2
USB電源KSY UB310-0520
ケース
ソリッドステートリレーSainSmart 2ch SSR
5Aヒューズ
USB無線LANWLI-UC-G301N
USBハブU2H-TZ420SBK
赤外線センサーHC-SR501
ジャンパワイヤメス-メス 10cmx6

 
[機能]
  • いつも天気予報を確認しつつ家を出るときには傘を忘れることが多いので、家を出るときにPiにアナウンスしてもらいます。
  • 赤外線センサーで人体検出します。
  • 天気予報をどこかのRSSから取得します。
  • リレー制御でスピーカー電源ONします。
  • 天気予報をアナウンスします。
  • スピーカー電源OFFします。
[メインプログラム]
  • 赤外線センサーは、VCC/GNDと、信号はGPIO4(ピン番号7)に接続しています。制御はeLinux-RPi Low-level peripheralsから持ってきてます。
  • SSR(ソリッドステートリレー)制御はGPIO17 (ピン番号11)で行います。機械式リレーだとAC100Vで使うのは難しく、トライしましたがスピーカーのボリュームを大きくするとリレーOFF時にPiが不安定になりました。
  • アナウンス起動後1分間は赤外線検出を無視します。深夜の帰宅時にしゃべられるとうるさいので時刻制限もつけています。
  • 実行しっぱなしにするため、daemon()でデーモン化し、入力ファイルのディレクトリもchdir()で直接指定します。ログはsyslog()を使います。ただし最初からデーモン化するとデバッグしづらいので、動作を確認出来た後、最後に入れます。
  • アナウンスは子供たちの声で録音しました。
    tenki.wav ... 「天気予報です!」
    ame.wav ... 「雨だよー」
    kasa.wav ... 「傘もったー!?」
    yuki.wav ... 「雪だよー!」
    kumori.wav ... 「曇りだよー!」
    hare.wav ... 「晴れだよー!」
  • RSSアクセスは、こちらを参考にしました。
  • ソースコード
    #include <stdio.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <stdlib.h>
    #include <netdb.h>
    #include <string.h>
    #include <time.h>
    #include <fcntl.h>
    #include <syslog.h>
    #include <sys/mman.h>
    #include <unistd.h>
    #include <sys/time.h>
    
    #define BCM2708_PERI_BASE        0x3F000000
    #define GPIO_BASE                (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */
    #define PAGE_SIZE (4*1024)
    #define BLOCK_SIZE (4*1024)
    
    //#define HOST "open.live.bbc.co.uk"
    #define HOST "天気予報サーバアドレス"
    #define RSSSIZE 8096
    #define PAGE "RSSファイル名"
    #define PORT 80
    #define USERAGENT "HTMLGET 1.0"
    
    // GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y)
    #define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
    #define OUT_GPIO(g) *(gpio+((g)/10)) |=  (1<<(((g)%10)*3))
    #define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
    #define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
    #define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0
    #define GET_GPIO(g) (*(gpio+13)&(1<<g)) // 0 if LOW, (1<<g) if HIGH
    #define GPIO_PULL *(gpio+37) // Pull up/pull down
    #define GPIO_PULLCLK0 *(gpio+38) // Pull up/pull down clock
    
    int  mem_fd;
    void *gpio_map;
    // I/O access
    volatile unsigned *gpio;
    void setup_io();
    
    int create_tcp_socket();
    int speakweather();
    char *get_ip(char *host);
    char *build_get_query(char *host, char *page);
    int get_weather(char *rssbuf, int *index, char *title);
    
    int main(int argc, char **argv)
    {
      if (daemon(1,0) == -1) {
          syslog(LOG_USER|LOG_INFO,"failed to launch daemon.\n");
          return -1;
      }
      chdir("/home/pi/cpp/speakweather");
    
      struct tm *s_time;
      time_t the_time;
      static int timecount=0;
      setup_io();
      INP_GPIO(4);
      INP_GPIO(17);
      OUT_GPIO(17);
    
      while(1){
        if(GET_GPIO(4) > 0 && timecount==0){
          (void) time(&the_time);
          s_time=localtime(&the_time);
          if(s_time->tm_hour>=6 && s_time->tm_hour<12){
            GPIO_SET = 1<<17;
            if(speakweather()!=0){
              syslog(LOG_USER|LOG_INFO,"speakweather error.");
              break;
            }
            GPIO_CLR = 1<<17;
            timecount=60;
          }
        }else if(timecount>0){
          timecount--;
        }
        sleep(1);
      }
      GPIO_CLR = 1<<17;
      return 0;
    }
    
    int speakweather()
    {
      struct sockaddr_in *remote;
      int sock;
      int tmpres;
      char *ip;
      char *get;
      char totalbuf[RSSSIZE+1];
      char buf[BUFSIZ+1];
      char titlebuf[BUFSIZ+1];
      char todayweekday[100];
      char todayweekdayjp[100];
      char host[]=HOST;
      char page[]=PAGE;
      char *tempchar;
      int titlenum;
      int index;
    
      time_t timer;
      struct tm *date;
      timer = time(NULL);
      date = localtime(&timer);
      strftime(todayweekday, sizeof(todayweekday), "%A", date);
      if (strcmp(todayweekday,"Sunday")==0){
        strcpy(todayweekdayjp,"日");
      }else
      if (strcmp(todayweekday,"Monday")==0){
        strcpy(todayweekdayjp,"月");
      }else
      if (strcmp(todayweekday,"Tuesday")==0){
        strcpy(todayweekdayjp,"火");
      }else
      if (strcmp(todayweekday,"Wednesday")==0){
        strcpy(todayweekdayjp,"水");
      }else
      if (strcmp(todayweekday,"Thursday")==0){
        strcpy(todayweekdayjp,"木");
      }else
      if (strcmp(todayweekday,"Friday")==0){
        strcpy(todayweekdayjp,"金");
      }else{
        strcpy(todayweekdayjp,"土");
      }
    
      sock = create_tcp_socket();
      ip = get_ip(host);
      remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *));
      remote->sin_family = AF_INET;
      tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr)));
      if( tmpres < 0)  
      {
        syslog(LOG_USER|LOG_INFO,"Can't set remote->sin_addr.s_addr");
        return -1;
      }else if(tmpres == 0){
        syslog(LOG_USER|LOG_INFO,"%s is not a valid IP addressn", ip);
        return -1;
      }
      remote->sin_port = htons(PORT);
    
      if(connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0){
        syslog(LOG_USER|LOG_INFO,"Could not connect");
        return -1;
      }
      get = build_get_query(host, page);
      
      //Send the query to the server
      int sent = 0;
      while(sent < strlen(get))
      { 
        tmpres = send(sock, get+sent, strlen(get)-sent, 0);
        if(tmpres == -1){
          syslog(LOG_USER|LOG_INFO,"Can't send query");
        }
        sent += tmpres;
      }
      //now it is time to receive the page
      memset(buf, 0, sizeof(buf));
      memset(totalbuf, 0, sizeof(totalbuf));
      char * htmlcontent;
      while((tmpres = recv(sock, buf, BUFSIZ, 0)) > 0){
        if(strlen(totalbuf)+strlen(buf) < RSSSIZE){
          strcat(totalbuf, buf);
        }
        memset(buf, 0, tmpres);
      }
      if(tmpres < 0)
      {
          syslog(LOG_USER|LOG_INFO,"Error receiving data");
      }
    
      htmlcontent = totalbuf;
      while(get_weather(htmlcontent, &index, titlebuf)==0){
        if(strstr(titlebuf, todayweekdayjp)!=NULL){
          printf("%s\n",titlebuf);
          system("aplay tenki.wav > /dev/null 2>&1");
          if(strstr(titlebuf,"雨")!=NULL){
            system("aplay ame.wav > /dev/null 2>&1");
            system("aplay kasa.wav > /dev/null 2>&1");
          }else
          if(strstr(titlebuf,"雪")!=NULL){
            system("aplay yuki.wav > /dev/null 2>&1");
          }else
          if(strstr(titlebuf,"曇")!=NULL){
            system("aplay kumori.wav > /dev/null 2>&1");
          }else
          if(strstr(titlebuf,"晴")!=NULL){
            system("aplay hare.wav > /dev/null 2>&1");
          }
          break;
        }
        htmlcontent += index + 15;
      }
      free(get);
      free(remote);
      free(ip);
      close(sock);
      return 0;
    }
    
    int create_tcp_socket()
    {
      int sock;
      if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
        syslog(LOG_USER|LOG_INFO,"Can't create TCP socket");
        exit(1);
      }
      return sock;
    }
    
    
    char *get_ip(char *host)
    {
      struct hostent *hent;
      int iplen = 15; //XXX.XXX.XXX.XXX
      char *ip = (char *)malloc(iplen+1);
      memset(ip, 0, iplen+1);
      if((hent = gethostbyname(host)) == NULL)
      {
        syslog(LOG_USER|LOG_INFO,"Can't get IP");
        exit(1);
      }
      if(inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, iplen) == NULL)
      {
        syslog(LOG_USER|LOG_INFO,"Can't resolve host");
        exit(1);
      }
      return ip;
    }
    
    char *build_get_query(char *host, char *page)
    {
      char *query;
      char *getpage = page;
      char tpl[] = "GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n";
      if(getpage[0] == '/'){
        getpage = getpage + 1;
        syslog(LOG_USER|LOG_INFO,"Removing leading \"/\", converting %s to %s\n", page, getpage);
      }
      // -5 is to consider the %s %s %s in tpl and the ending \0
      query = (char *)malloc(strlen(host)+strlen(getpage)+strlen(USERAGENT)+strlen(tpl)-5);
      sprintf(query, tpl, getpage, host, USERAGENT);
      return query;
    }
    
    int get_weather(char *rssbuf, int *index, char *title)
    {
      char* ret;
      char* retend;
      ret = strstr(rssbuf, "<title>");
      if(ret!=NULL){
        ret+=7;
        retend = strstr(ret, "</title>");
        if(retend!=NULL && retend-ret<BUFSIZ){
          strncpy(title, ret, retend-ret);
          *index=retend-rssbuf;
          return 0;
        }
      }
      return -1;
    }
    
    //
    // Set up a memory regions to access GPIO
    //
    void setup_io()
    {
       /* open /dev/mem */
       if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
          syslog(LOG_USER|LOG_INFO,"can't open /dev/mem ");
          exit(-1);
       }
    
       /* mmap GPIO */
       gpio_map = mmap(
          NULL,             //Any adddress in our space will do
          BLOCK_SIZE,       //Map length
          PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory
          MAP_SHARED,       //Shared with other processes
          mem_fd,           //File to map
          GPIO_BASE         //Offset to GPIO peripheral
       );
    
       close(mem_fd); //No need to keep mem_fd open after mmap
    
       if (gpio_map == MAP_FAILED) {
          syslog(LOG_USER|LOG_INFO,"mmap error %d", (int)gpio_map);//errno also set!
          exit(-1);
       }
    
       // Always use volatile pointer!
       gpio = (volatile unsigned *)gpio_map;
    
    
    } // setup_io
    
    
[デーモン化]
  • /etc/init.d/の下に実行ファイル作成。humandetectorとして保存。INIT INFOのところはコメント文ですが後述のupdate-rc.dコマンドで使用されるため必要です。/lib/lsb/init-functionsもstatus_of_procコマンドで必要です。
    #! /bin/sh
    ### BEGIN INIT INFO
    # Provides:          speakweather
    # Required-Start:    $remote_fs $syslog
    # Required-Stop:     $remote_fs $syslog
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    ### END INIT INFO
    
    PATH=/sbin:/usr/sbin:/bin:/usr/bin
    DESC="speakweather"
    NAME=speakweather
    DAEMON=/home/pi/cpp/speakweather/$NAME
    SCRIPTNAME=/etc/init.d/$NAME
    
    # Exit if the package is not installed
    [ -x "$DAEMON" ] || exit 0
    
    # Define LSB log_* functions.
    # Depend on lsb-base (>= 3.2-14) to ensure that this file is present
    # and status_of_proc is working.
    . /lib/lsb/init-functions
    
    #
    # Function that starts the daemon/service
    #
    do_start()
    {
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --exec $DAEMON \
                || return 2
        return 0
    }
    
    #
    # Function that stops the daemon/service
    #
    do_stop()
    {
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --name $NAME
        RETVAL="$?"
        return "$RETVAL"
    }
    
    case "$1" in
      start)
        do_start
        ;;
      stop)
        do_stop
        ;;
      status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
      *)
        echo "Usage: $SCRIPTNAME {start|stop|status}" >&2
        exit 3
        ;;
    esac
    
    :
  • update-rc.dコマンドでリンク作成。/etc/rc0.d/~/etc/rc6.d/の下にリンクが作成されます。これで、rebootするとspeakweatherが起動します。
    pi@raspberrypi ~ $ sudo update-rc.d speakweather defaults
    update-rc.d: using dependency based boot sequencing
    pi@raspberrypi ~ $ ls /etc/rc[0-6].d/*speakweather*
    /etc/rc0.d/K01speakweather  /etc/rc2.d/S02speakweather  /etc/rc4.d/S02speakweather  /etc/rc6.d/K01speakweather
    /etc/rc1.d/K01speakweather  /etc/rc3.d/S02speakweather  /etc/rc5.d/S02speakweather
  • 停止させたり、実行されているかどうか確認したい場合、serviceコマンドを使います。
    pi@raspberrypi ~ $ sudo service speakweather start
    pi@raspberrypi ~ $ sudo service speakweather status
    [ ok ] speakweather is running.
    pi@raspberrypi ~ $ sudo service speakweather stop
    pi@raspberrypi ~ $ sudo service speakweather status
    [FAIL] speakweather is not running ... failed!


TOP PAGE