Let's take photos.
We buy a USB webcam for a reasonable price.
We install the v4l-utils package and the fswebcam app and connect the webcam to the Raspberry Pi USB port.
The video0 device should appear in the /dev location.
We issue the following command:
fswebcam -d /dev/video0 -r 640x480 --jpeg 85 /home/rcp/zdjecia/zdjecie.jpg
The webcam blinks a green diode and the zdjecie.jpg file appears in the /home/rcp/zdjecia directory.
It is that simple, the photo is taken and we could end this post here.
However, we will show how to make a Raspberry Pi camera server that supports several cameras taking photos at the request of another program that runs somewhere on the network, on another server.
We implemented this solution in our sobriety checkpoint system.
After turning on the breathalyzer, the system takes three photos of the person taking a test to eliminate cases of blowing on behalf of a friend who is not in the best shape today.
At the beginning, an important, in my opinion, note.
In Raspberry Pi, there is an SD card instead of a disk. It is a good solution, those cards are fast, capacious...
However, they have one significant disadvantage. They wear out when they are written to. The number of write cycles that the card can withstand is counted in tens or even hundreds of thousands.
In a mobile phone, it should last for many years.
However, we are planning to make a camera server, taking possibly tens of thousands of photos a day.
With such a large number of writes, after a week, it may turn out that the card did not withstand it.
We will fix this issue by creating a tiny filesystem in RAM memory.
In the /etc/fstab file we add the line:
none /home/rcp/zdjecia tmpfs size=1M,noatime 0 0
After starting the system, or issuing the mount -a command, in the /home/rcp/zdjecia directory, will be mounted a small, only 1MB, file system in RAM memory.
Here we can write as much as we want without fear of destroying the card.
Of course, the mount point, so the /home/rcp/zdjecia directory, must be created beforehand!
We connect more cameras to Raspberry Pi, in the /dev location appear next devices video1, video2...
Excellent, but how to tell which camera is taking photos of the door and which is in the window?
We connected the one in the window to USB4, the one facing the door to USB2. The following command from the v4l-utils package helps:
root@mobile3:~# v4l2-ctl --list-devices
UVC Camera (046d:081b) (usb-3f980000.usb-1.4):
/dev/video0
UVC Camera (046d:081b) (usb-3f980000.usb-1.5):
/dev/video1
Now we know which video device from the /dev location is connected to which USB port.
All we need is a simple program that converts the USB port number to the name of a video device and we can take pictures.
In what the v4l2-ctl tool produces, it looks up the USB port number and returns the name of the correct video device:
sprintf(wzor,"usb-1.%1d" , atoi(argv[1])); while( fgets(buf, 50, stdin) != NULL) { if ( strstr(buf, wzor) != NULL) { fgets(buf, 50, stdin); printf("%s", buf); exit(0); } } } //od main
So now some good old shell.
The pstryk script:
#!/bin/sh
cd /home/rcp
DEV=`v4l2-ctl --list-devices|./znajdz $1`
/usr/bin/fswebcam -d $DEV -r 640x480 --jpeg 85 /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`.jpg >/dev/null 2>&1
/usr/bin/fswebcam -d $DEV -r 640x480 --jpeg 85 /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`a.jpg >/dev/null 2>&1
/usr/bin/fswebcam -d $DEV -r 640x480 --jpeg 85 /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`b.jpg >/dev/null 2>&1
scp -p32 /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`*.jpg bk@192.168.0.100:/var/www/html/skt/kamera >/home/rcp/kamera.log 2>&1
rm /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`*.jpg
How does it work?
We call it with two parameters, the camera number and the name with which to save the photos, so for example:
./pstryk 3 zdjęcie2
- The described earlier znajdz.c program will replace the camera number with its camera name found in the /dev location.
- It will take 3 photos, saving them with the names (in this example) zdjecie2.jpg, zdjecie2a.jpg, zdjecie2b.jpg in the /home/rcp/zdjecia (tmpfs) directory.
- It will upload those photos to the server saving them in the /var/www/html/skt/kamera directory.
- It will delete the local copies of the photos, because our local filesystem is tiny, only 1Mb!
The scp command copies files to the remote server, but in order not to ask for a password, we need to configure the server and the ssh client (both on the remote and local machines) to authenticate with public keys as described in the ssh man.
Well, we already know how to take photos and transfer them to the server's disk. It is time for a program that will turn our fantastic Raspberry Pi into a camera server.
We will analyze the kamera.c program in a moment, but if you feel that I will bore you to death with this, I will just say that all you need to do is to download it, compile it, and run it:
cc kamera.c -o kamera
./kamera 5095&
Now we can remotely take a photo by connecting to our Raspberry Pi, for example, using the telnet protocol:
telnet 192.168.0.33 5095 Trying 192.168.0.33... Connected to 192.168.0.33. Escape character is '^]'. 5 KUKU
5 is the number of the USB port to which the camera is plugged in, "KUKU" is the name under which to save the photo.
If we have not yet configured the keys for scp, it is good to comment out the pstryk script's line that deletes the captured photo:
#rm /home/rcp/zdjecia/`echo $2|sed -e 's/[[:space:]]//'`*.jpg
Now, you should be able to find our photos in the dedicated for them directory:
root@mobile3:/home/rcp/zdjecia# ls -l /home/rcp/zdjecia/ total 36 -rw-r--r-- 1 root root 12124 Jun 30 12:57 KUKUa.jpg -rw-r--r-- 1 root root 11975 Jun 30 12:57 KUKUb.jpg -rw-r--r-- 1 root root 11133 Jun 30 12:57 KUKU.jpg
Of course, in the production environment, the camera number and photo name will be sent to our server by another program running on some other computer on the network.
Now the server program. The complete source code of the kamera.c program can be found under the previously presented link, so we will skip those parts that do not require explanation, such as header files or variable definitions.
Our program, same as any decent server, should constantly receive requests from the client programs to take a photo. The process of taking a photo, however, takes a few seconds, the camera has to assess the quality of the lighting, sharpen the image, etc.
It would be difficult for the server to handle the clients' requests at the same time, so in order to take a photo, each time we run a child process that takes care of it.
However, once the child process has finished its work, it still remains in the system as a so-called zombie process. Not whole, but some of its parts, but it remains.
The point is that the parent process may want to know, for example, if the child process has terminated with an error.
The zombie process will be deleted after the parent process terminates, but if, as in our case, the parent process is running constantly, we have to take care of the child process removal.After the child process terminates, a SIGCHLD signal is sent to the parent process. We handle it, which removes the zombie process.
Storing the process termination status in the global variable is optional. It can be used, for example, to handle events like failure to take a photo.
The interrupt handler procedure looks like this:
sig_atomic_t child_exit_status; void clean_up_child_process(int signal_num) { /* remove child process */ wait (&status); /* save exit status in a global variable */ child_exit_status = status; }
It is used in the main procedure as follows:
main (argc, argv) int argc; char *argv[]; { /* handle SIGCHLD by calling clean_up_child_process */ struct sigaction sigchild_action; memset(&sigchild_action, 0, sizeof(sigchild_action)); sigchild_action.sa_handler = &clean_up_child_process; sigaction(SIGCHLD, &sigchild_action, NULL);
Now we will make a server listening, on the selected port passed as a parameter when starting our program, for any requests to take a photo:
int sockfd, newsockfd, portno, clilen; struct sockaddr_in serv_addr, cli_addr;We create a socket:
sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("ERROR opening socket"); exit(1); } bzero((char *) &serv_addr, sizeof(serv_addr));In the portno variable we store the port number on which our server is listening:
portno = atoi(argv[1]); //arbitralny zakres 5090 - 5099 serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno);We bind an address to our socket:
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("ERROR on binding"); exit(1); }And we start our server. It will listen to the clients' requests on the indicated port.
The number 5 is the length of the queue of the clients waiting for the connection:
listen(sockfd,5); clilen = sizeof(cli_addr); while (TRUE) {And now, we are waiting for the client. When it appears, accept will create a new socket (newsockfd) which will be used to communicate with this client:
restart: newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen); if (newsockfd < 0) { if (errno == EINTR) goto restart; perror("ERROR on accept"); exit(1); }
We already have a connection with the client, we receive the number of the camera that will take the photo and the name under which to save it:
/* czytaj rozkazy z serwera */ bzero(buf,10); // Odczytaj numer portu usb kamery time (&czas1); time (&czas2); m=0; while (czas2 - czas1 < 5) // 5 sekund na odczyt, potem timeout { if ((read (newsockfd, &buf[m], 1)) != -1) { if( m>100) break; // padl port if (buf[m] == 0x0a) { buf[m+1]=0x0; wynik=1; break; } m++; } time (&czas2); } // Odczytaj numer zdjecia time (&czas1); time (&czas2); m=0; while (czas2 - czas1 < 5) // 5 sekund na odczyt, potem timeout { if ((read (newsockfd, &buf1[m], 1)) != -1) { if( m>100) break; // padl port if (buf1[m] == 0x0a) { buf1[m+1]=0x0; wynik=1; break; } m++; } time (&czas2); }
So, now we take photos using the shell script described previously.
We run it using the execl function, providing the camera number and name of the photo as parameters.
Using the fork() function will create a process that is a copy of the process that called it.
The return value of the fork() function tells us whether we are in the parent process or in the child process.
In the parent process, it is the PID value of the child process, and in the child process, it is the value 0.
Therefore, the execl function will only execute in the child process, which can take photos without rushing.
The parent process closes the network connection with the client and waits for the next one to be handled.
if (! (m = fork()) ) execl("/home/rcp/pstryk", "/home/rcp/pstryk", buf, buf1, (char *)0); shutdown(newsockfd, 2); close(newsockfd); } //od while true