Friday, December 28, 2012

Simple TCP client server sockets application using IPv6 and IPv6

So here is a pair of programs demonstrating the use of sockets on Linux. The server program is run first. Then whenever the client program is run, it connects to the server on the specified port and they both exchange strings and terminate. TCP is used over IPv4.

Simple_tcpserver_ipv4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <arpa/inet.h>

void error(char *msg) {
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n;


    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(0);
    }

    printf("\nIPv4 TCP Server Started...\n");

    //Sockets Layer Call: socket()    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");
    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    
    //Sockets Layer Call: bind()
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    //Sockets Layer Call: listen()
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    
    //Sockets Layer Call: accept()
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0)
        error("ERROR on accept");

    //Sockets Layer Call: inet_ntoa()

    printf("Incoming connection from client having IPv4 address: %s\n",    inet_ntoa(cli_addr.sin_addr));

    memset(buffer,0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(newsockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");

    printf("Message from client: %s\n", buffer);

    //Sockets Layer Call: send()
    n = send(newsockfd, "Server got your message", 23+1, 0);
    if (n < 0)
        error("ERROR writing to socket");
    
    //Sockets Layer Call: close()
    close(sockfd);
    close(newsockfd);
    
    return 0;
}

Simple_tcpclient_ipv4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void error(char *msg) {
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[]) {
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;
    char buffer[256] = "This is a string from client!";

    if (argc < 3) {
        fprintf(stderr, "Usage: %s  \n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);

    printf("\nIPv4 TCP Client Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    //Sockets Layer Call: gethostbyname()
    server = gethostbyname(argv[1]);
    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    memset((char *) &serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    memmove((char *) &serv_addr.sin_addr.s_addr, (char *) server->h_addr, server->h_length);
    serv_addr.sin_port = htons(portno);

    //Sockets Layer Call: connect()
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");

    
    //Sockets Layer Call: send()
    n = send(sockfd,buffer, strlen(buffer)+1, 0);
    if (n < 0)
        error("ERROR writing to socket");

    memset(buffer, 0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(sockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");
    printf("Message from server: %s\n", buffer);

    //Sockets Layer Call: close()
    close(sockfd);
        
    return 0;
}
Makefile_tcpipv4:
all: 
    gcc Simple_tcpserver_ipv4.c -o Simple_tcpserver_ipv4.out
    gcc Simple_tcpclient_ipv4.c -o Simple_tcpclient_ipv4.out

clean:
    rm Simple_tcpserver_ipv4.out
    rm Simple_tcpclient_ipv4.out

Execution results on Ubuntu:



In the above screenshot, I have shown both the client and server applications running on the same PC and so while running the client application, we use the local loopback address 127.0.0.1.

Now if we need to port the above pair of applications from IPv4 to IPv6, we need to make subtle changes in the code. When such changes are made to the above pair of applications, they would look as below. Changes are highlighted in red.


Simple_tcpserver_ipv6.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <arpa/inet.h>

void error(char *msg) {
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in6 serv_addr, cli_addr;
    int n;
    char client_addr_ipv6[100];

    if (argc < 2) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(0);
    }

    printf("\nIPv6 TCP Server Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = atoi(argv[1]);
    serv_addr.sin6_flowinfo = 0;
    serv_addr.sin6_family = AF_INET6;
    serv_addr.sin6_addr = in6addr_any;
    serv_addr.sin6_port = htons(portno);

    
    //Sockets Layer Call: bind()
    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");

    //Sockets Layer Call: listen()
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    
    //Sockets Layer Call: accept()
    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    if (newsockfd < 0)
        error("ERROR on accept");

    //Sockets Layer Call: inet_ntop()
    inet_ntop(AF_INET6, &(cli_addr.sin6_addr),client_addr_ipv6, 100);
    printf("Incoming connection from client having IPv6 address: %s\n",client_addr_ipv6);

    memset(buffer,0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(newsockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");

    printf("Message from client: %s\n", buffer);

    //Sockets Layer Call: send()
    n = send(newsockfd, "Server got your message", 23+1, 0);
    if (n < 0)
        error("ERROR writing to socket");
    
    //Sockets Layer Call: close()
    close(sockfd);
    close(newsockfd);
    
    return 0;
}
 Simple_tcpclient_ipv6.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void error(char *msg) {
    perror(msg);
    exit(0);
}

int main(int argc, char *argv[]) {
    int sockfd, portno, n;
    struct sockaddr_in6 serv_addr;
    struct hostent *server;
    char buffer[256] = "This is a string from client!";

    if (argc < 3) {
        fprintf(stderr, "Usage: %s  \n", argv[0]);
        exit(0);
    }
    portno = atoi(argv[2]);

    printf("\nIPv6 TCP Client Started...\n");
    
    //Sockets Layer Call: socket()
    sockfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (sockfd < 0)
        error("ERROR opening socket");

    //Sockets Layer Call: gethostbyname2()
    server = gethostbyname2(argv[1],AF_INET6);
    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    memset((char *) &serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin6_flowinfo = 0;
    serv_addr.sin6_family = AF_INET6;
    memmove((char *) &serv_addr.sin6_addr.s6_addr, (char *) server->h_addr, server->h_length);
    serv_addr.sin6_port = htons(portno);

    //Sockets Layer Call: connect()
    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR connecting");

    
    //Sockets Layer Call: send()
    n = send(sockfd,buffer, strlen(buffer)+1, 0);
    if (n < 0)
        error("ERROR writing to socket");

    memset(buffer, 0, 256);
    
    //Sockets Layer Call: recv()
    n = recv(sockfd, buffer, 255, 0);
    if (n < 0)
        error("ERROR reading from socket");
    printf("Message from server: %s\n", buffer);

    //Sockets Layer Call: close()
    close(sockfd);
        
    return 0;
}
Makefile_tcpipv6:
all: 
    gcc Simple_tcpserver_ipv6.c -o Simple_tcpserver_ipv6.out
    gcc Simple_tcpclient_ipv6.c -o Simple_tcpclient_ipv6.out

clean:
    rm Simple_tcpserver_ipv6.out
    rm Simple_tcpclient_ipv6.out

Execution results on Ubuntu:




Source code(s):
Archive containing the above files can be downloaded from here.

Here is another version of the code which uses UDP instead of TCP. uses recvfrom() and sendto(). In this case only the client sends the string to server - the server does not send a string back.

Here is the source code for another pair of applications which implement a sort of chat server and its chat clients. Multiple chat clients can connect to a single chat server. Whatever string one client sends to the server, it is forwarded to all the clients. select() is used. The archive contains both IPv4 as well as IPv6 implementations.

Note: If you are going to be using link local IPv6 addresses with above sample applications, make sure to set serv_addr.sin6_scope_id = 0,1,2,3...(whatever is the interface number of your network adapter) in the client code. Read more here.

References:

Sunday, December 9, 2012

Script for uploading DHT11 readings to Cosm using Curl/JSON on Raspberry Pi running Arch Linux

Earlier, I have written on how to get the Vodafone 3G USB Modem (K3770-Z) working on Raspberry Pi here. After I was done doing that, I decided to create a shell script to upload sensor data from DHT11 to cosm.com over the internet connection provided by the Vodafone 3G USB Modem.




Here are is how I got that done:
NOTE:  Execute all commands on Raspberry Pi unless otherwise specified. I referred to the following two Adafruit tutorials while doing this: DHT Humidity Sensing on Raspberry Pi with GDocs Logging and end Raspberry Pi Data to COSM

  1. Assemble the setup as shown in the photo above and follow the instructions here (Getting Vodafone 3G to work on Raspberry Pi in India using K3770-Z) to get the Vodafone 3G USB Modem working on your Raspberry Pi. I use the Arch Linux distribution. Make sure to create sakis3g.conf to automate the Sakis3G script. Also ensure that the sakis3g script is in your root's home directory (/root) and has the execute bit set before your proceed. Your Raspberry Pi will need internet access to download some of the packages and files in the following steps.
  2. Install the development packages (includes compiler, libraries and related utilities to compile programs on Raspberry Pi for Raspberry Pi:
    pacman -S base-devel
  3. Next we need to install a library which will allow us to control GPIO pins of Raspberry Pi from our C programs: bcm2835 library. The latest version of this library at the time of writing this was 1.14. Execute the following commands to download the source, compile and install the library. Do this while logged in as root and while in the root's home folder (/root):
    wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.14.tar.gz
    tar -zxvf bcm2835-1.14.tar.gz
    cd bcm2835-1.14
    ./configure
    make
    make install
  4. Now we need the C program source and it Makefile which will read the temperature and humidity values from DHT11 sensor (these files are modified versions of sources taken from Adafruit's repository on github):
    cd ~

    wget 'https://raw.github.com/lithiumhead/RPi-cosm-curl-DHT11/master/DHT.c'

    wget 'https://raw.github.com/lithiumhead/RPi-cosm-curl-DHT11/master/Makefile'
  5. Compile the C program:
    make
  6. And execute it (assuming you too are using DHT11 connected to pin 4 of Raspberry Pi with a 4.7K pull-up):
    ./DHT.out 11 4
  7. If the program executed successfully, you will see the output similar to:
    OK 28 36
    where OK specifies that the communication with DHT11 was successful (if not, there will be no output displayed on the screen) and the two numbers after that: 28 and 36 are temperature (in Celsius) and relative humidity (in %) readings respectively.
  8. Next step is to install curl:
    pacman -S curl
  9. Next download the bash script which will upload the readings to cosm.com:
    wget 'https://raw.github.com/lithiumhead/RPi-cosm-curl-DHT11/master/cosm-curl-DHT11.sh'
  10. Edit this script and change the values of the API_KEY and the FEED_ID to match those of your own feed. You may also want to update the various parameters of the JSON structure in the script for your own specific settings: title, website, lat, lon etc..:
    nano cosm-curl-DHT11.sh
  11. Ctrl+O to save the file 
  12. Ctrl+X to exit
  13. Set the execute bit on the script:
    chmod +x cosm-curl-DHT11.sh
  14. And execute the script:
    bash cosm-curl-DHT11.sh
  15. The script connect to the internet using the Vodafone 3G USB Modem and start uploading the readings to cosm.com every 10 seconds.
  16. You can stop the script by pressing Ctrl+C
Output of the cosm-curl-DHT11.sh script executing on Raspberry Pi as observed over ssh terminal


NOTES:
  • DHT11 doesn't always reply with valid temperature and humidity data and so sometimes the C program which tries to read data from it will not respond with any temperature and humidity values. In such cases, the script will simply wait for 10 more seconds before trying to execute the program (DHT11.out) again
  • For instructions on how to create a new feed on cosm.com, refer to this section from Adafruit's tutorial on Sending Raspberry Pi Data to COSM. It is essential that you select the device/feed as Arduino if you want to see the feed ID and the API key for the new feed that you are creating.

Wednesday, December 5, 2012

Visio and Word files misbehaving with GitHub

All GitHub repositories are initialized with a default .gitattributes file which has the following configuration to handle Microsoft Word files:

*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain

This configuration works fine as long as the Word files in your repositories have only text and tables. The moment you try to commit Word files with images embedded in them or Microsoft Visio files, the line ending conversion and diff applied to them by Git will probably corrupt them. The files will open properly on your computer, but if you try to clone the repository on a different computer and try to open those files you will probably encounter the following messages:
Corrupted Microsoft Word file: The file cannot be opened because there are problems with the contents

Corrupted Microsoft Visio file: An error (100) occurred during the action Open File. Visio cannot open the file because it's not a Visio file or it has become corrupted.
To fix this we need to add/modify the following lines in the .gitattributes files present in that particular repository (if you are using GitHub for Windows, you can do this easily by selecting Options>Settings... for that particular repository):


*.doc binary
*.DOC binary
*.docx binary
*.DOCX binary
*.vsd binary
*.VSD binary





This will tell GitHub to handle the Mircosoft Word files and Mircosoft Visio file as binary files and not apply line ending conversions or diff to them. You should create similar configurations for image files like JPEGs, PNGs and GIFs

More information here: Customizing Git - Git Attributes

Sunday, December 2, 2012

Getting Vodafone 3G to work on Raspberry Pi in India using K3770-Z

I live in Pune, India and I wanted to do this: DHT Humidity Sensing on Raspberry Pi with GDocs Logging

So I assembled this:


and faced two problems: 


  1. I couldn't immediately figure out how to get K3770-Z to work on Raspberry Pi. Since this K3770-Z 3G USB Modem from Vodafone worked on Ubuntu 12.10 x86 without requiring any installation or configuration, I was sure that I could somehow get it to work on Raspberry Pi
  2. When I finally did get the modem to work on RPi, I found that the iBall Portable Charger (Datasheet here) could not source the peak current requirements of the modem when it turns on registers to the 3G network. The charger can supply only 600mA @ 5V where as the 3G modem definitely requires a lot more during registration.
The second problem can be solved simply by using a 12V 2.2Ah Sealed Lead Acid Battery and a 12V to 5V DC-DC converter. For the conversion you could use the car accessory for charging phones or build one yourself using LM2576.

As for the K3770-Z, here is how I got it to work on Arch Linux.

Things you need:

  1. 5V DC power supply capable of supplying current greater than 1.0 Ampere
  2. Raspberry Pi
  3. powered USB Hub (Raspberry Pi by itself will not be able to supply enough current to the modem)
  4. K3770-Z
  5. SD Card (atleast 4GB)
  6. TV or Monitor with HDMI input
  7. HDMI Cable
  8. USB Keyboard
  9. A Windows or Linux PC with an SD Card reader
  10. Internet connection for the Raspberry Pi over Ethernet
Instructions (ours will be bit different from the ones given over at 3G Internet on Raspberry Pi - Success!, also order of executing the instructions is important):
  1. Using your laptop, download the Arch Linux image from here: http://www.raspberrypi.org/downloads and write it to an SD Card using Win32DiskImager (if using Windows) or dd (if using Linux).
  2. Insert the SD Card into the Raspberry Pi and supply it with an internet connection using an Ethernet cable connected to your router. You could also connect the Raspberry Pi to your computer using a cross Ethernet cable and use Windows Internet Connection Sharing to give internet access to your Raspberry Pi. Connect the required peripherals (USB Hub, Modem, Keyboard, Monitor etc.) to the Raspberry Pi and power it up. Carry out the remaining instructions on the Raspberry Pi itself.
  3. Login into Arch Linux (login: root, password: root)
  4. Make sure your Raspberry Pi has internet connectivity (using Ethernet) by trying ping:
    ping www.google.com
  5. Restart the Raspberry Pi once
  6. Update pacman package lists:
    pacman -Syy
  7. Update pacman:
    pacman -S pacman
  8. DO NOT update all the system packages blindly by executing pacman -Syu
    Instead only install the packages required for the 3G connection:
    pacman -S ppp
    pacman -S wvdial
    pacman -S usb_modeswitch
  9. Our K3770-Z modem has a Vendor ID of 0x19d2 and Product ID of 0x1175. This product ID corresponds to a Virtual CD-ROM device. We must switch K3770-Z to modem mode (to product ID 0x1177) if we want to use it to access the internet. To do this create a configuration file for it:nano /etc/usb_modeswitch.d/19d2\:1175
  10. Type the following lines into this new configuration file:
    # Vodafone (ZTE) K3770-Z
    
    DefaultVendor= 0x19d2
    DefaultProduct=0x1175
    
    TargetVendor=  0x19d2
    TargetProduct= 0x1177
    
    MessageContent="5553424312345678000000000000061b000000020000000000000000000000"
    
    NeedResponse=1
    
    CheckSuccess=20
  11. Ctrl+O to save the file 
  12. Ctrl+X to exit
  13. Now we need to download the "binary free" version of Sakis3G script ( we need binary free version as we want it to use the system supplied version of usb_modeswitch):
    wget "http://www.sakis3g.org/versions/latest/binary-free/sakis3g.gz"
  14. Uncompress the script:gunzip sakis3g.gz
  15. Make the file executable:
    chmod +x sakis3g
  16. Now you can disconnect the Ethernet cable from Raspberry Pi and try to use the 3G modem to access the internet (make sure the network coverage in your area is good enough, use a Windows PC to connect to the internet using your 3G Modem and check if it is working fine before carrying out the instructions):
    ./sakis3g --interactive
  17. An interactive menu driven interface will help you connect to the internet. Some of the configuration options that need to be selected are as below:
    1. USB Interface: Select Interface #1
    2. APN: Custom APN
    3. Enter "www" for APN.
    4. For username and password just enter "123" and "456" respectively. Actually it doesn't matter what username and password you enter, you can enter anything.
  18. If you followed the steps correctly, you should be connected to the internet and you should be able to ping www.google.com successfully.
To automate the Sakis3G script so that you don't have to manually supply the options, you can create a configuration file (execute nano /etc/sakis3g.conf) containing the following lines:
--console
--debug
--noprobe
--pppd
--googledns
MODEM="19d2:1175"
USBINTERFACE="1"
FORCE_APN="www"
APN_USER="123"
APN_PASS="456"
DIAL="*99***1#"

The debug option will spew out on the monitor the internal details of the sakis3g script on the screen as it executes. After you have created the script, you can execute ./sakis3g connect to connect to the internet without manual intervention.
The above configuration file is enough to connect quickly and easily to the Vodafone 3G network. You can refer to the Sakis3G configuration page here for more options that you can configure.

I captured the debug messages generated by sakis3g in a log file during a successful connect. It is here.

And as for the power supply problem, here is what my new setup looks like, and it works fine as expected - the 2.2Ah 12V rechargeable Valve Regulated Lead Acid battery is able to supply enough current to power the Raspberry Pi and the USB 3G Modem. For voltage regulation I used: an LM2576-5.0, 2nos. 1N5819 in parallel & a 150uH 1.5A Inductor.