title


Raspberry Pi
[Weather Forecast Announcement System with Passive Infrared Sensor]
system
 
[Block Diagram]
connect
Raspberry Pi2
USB power supplyKSY UB310-0520
Case
Solid State RelaySainSmart 2ch SSR
5A Fuse
USB Wireless LANWLI-UC-G301N
USB hubU2H-TZ420SBK
Passive infrared sensorHC-SR501
Jumper wirefemale-female 10cmx6

 
[Functions]
  • I watch the weather forecast every day but I often forget to bring my umbrella when I go out. So, Pi should remind that to me just before I go out..
  • Pi detects a human by a passive infrared sensor.
  • Pi gets the weather forecast from Yahoo! Japan weather RSS.
  • Pi turns on the speaker by the relay control.
  • Pi annouces the weather forecast.
  • Pi turns off the speaker.
[Install Raspbian]
[Remote access]
[USB Wireless LAN]
[Main Program]
  • Passive infrared sensor is connected to VCC/GND and GPIO4 (pin number 7). I used eLinux-RPi Low-level peripherals for GPIO controls.
  • Solid State Relay is connected to GPIO17 (pin number 11). The mechanical relay is cheaper but that is not easy to use for AC100V control. I tried once but Pi became unstable when the relay turned off at a bigger speaker volume setting.
  • Sensor detection is ignored for one minite after the previous announcement.
  • Use daemon() to run this program for the announcement system. The input directory is defined directly by chdir(). Use syslog() for logging. But, the daemon should be added at the end after all debugs.
  • Use Chinese Character because Yahoo! JAPAN weather forecast RSS is in Japanese.
  • The announcement voices were recorded with my sons.
    tenki.wav ... "Weather forecast!"
    ame.wav ... "It will rain..."
    kasa.wav ... "Don't forget the umbrella!"
    yuki.wav ... "It will be snow!"
    kumori.wav ... "It will be cloudy!"
    hare.wav ... "It will be sunny!"
  • RSS access programming. Referred here.
  • source code
    #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 "Weather Information Server Address"
    #define RSSSIZE 8096
    #define PAGE "RSS file name"
    #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
    
    
[Daemonize]
  • Create speakweather under /etc/init.d/. Comment sentences in INIT INFO are necessary for update-rc.d command later. /lib/lsb/init-functions is also necessary for status_of_proc command.
    #! /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
    
    :
  • Make link files by update-rc.d command. The link files will be created in /etc/rc0.d/~/etc/rc6.d/. After that, speakweather will run automatically after the boot.
    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 command can controll speakweather daemon.
    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!