Monday, February 4, 2013

Web connected outlet - part 6


The software

The WebIOPi software, developed by Eric Ptak and featured in this month's MagPi magazine, has some great, easy to use software for manipulating GPIO ports.  If you're doing GPIO, you owe it to yourself to check this out.  Most of the code below started from the WebIOPi examples, except for the C code (which started out as code by Kevin Sangeelee, released as GPLv2.)

I've skinnied down his WebIOPi Javascript code to simply turn on and off a single port.  These web apps are stored in /usr/share/webiopi/htdocs on the RPi and are called, imaginatively, on.html and off.html. Here's what they look like:

on.html

<html>
<head>
<title>Switch 1 on</title>
    <script type="text/javascript" src="webiopi.js"></script>
    <script type="text/javascript">
        webiopi().ready(function() {
            webiopi().setFunction(7,"OUT");
            webiopi().setValue(7, 1);
        });
    </script>
</head>
<body>
<FORM METHOD="post">
    <INPUT TYPE="button"
        VALUE="Back"
        OnClick="history.go(-1);return true;"">
</FORM>
</body>
</html>


off.html

<html>
<head>
<title>Switch 1 off</title>
    <script type="text/javascript" src="webiopi.js"></script>
    <script type="text/javascript">
        webiopi().ready(function() {
            webiopi().setFunction(7,"OUT");
            webiopi().setValue(7, 0);
        });
    </script>
</head>
<body>
<FORM METHOD="post">
    <INPUT TYPE="button"
        VALUE="Back"
        OnClick="history.go(-1);return true;"">
</FORM>
</body>
</html>


They're really quite simple and they work. I picked GPIO 7 because it's the last pin in the header on the outboard side. (It's the easiest to locate without counting pins.)

From a browser just access these two web pages to turn on and off port 7:

http://192.168.1.210:8000/on.html
http://192.168.1.210:8000/off.html

I can also call these from a web page running on my mac from a couple of buttons. The html on the mac looks like this:

<html>
<body>
<FORM METHOD="post">
    Switch 1
    <INPUT TYPE="button"
        VALUE="On"
        OnClick="window.location='http://192.168.1.210:8000/on.html'">
    <INPUT TYPE="button"
        VALUE="Off"
        OnClick="window.location='http://192.168.1.210:8000/off.html'">
</FORM>
</body>
</html>

Pretty simple... Obviously, you can make these web pages arbitrarily complex, or you could just use the demo code that Eric provides. But for this purpose, the simpler the better.

There are also libraries that allow you to do GPIO manipulation in C.  Here's a C program that turns on and off port 7:


#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#define IOBASE   0x20000000
#define GPIO_BASE (IOBASE + 0x200000)
#define GPFSEL0    *(gpio.addr + 0)
#define GPFSEL1    *(gpio.addr + 1)
#define GPFSEL2    *(gpio.addr + 2)
#define GPFSEL3    *(gpio.addr + 3)
#define GPFSEL4    *(gpio.addr + 4)
#define GPFSEL5    *(gpio.addr + 5)
// Reserved @ word offset 6
#define GPSET0    *(gpio.addr + 7)
#define GPSET1    *(gpio.addr + 8)
// Reserved @ word offset 9
#define GPCLR0    *(gpio.addr + 10)
#define GPCLR1    *(gpio.addr + 11)
// Reserved @ word offset 12
#define GPLEV0    *(gpio.addr + 13)
#define GPLEV1    *(gpio.addr + 14)
#define BIT_7 (1 << 7)
#define PAGESIZE 4096
#define BLOCK_SIZE 4096

struct bcm2835_peripheral {
    unsigned long addr_p;
    int mem_fd;
    void *map;
    volatile unsigned int *addr;
};

struct bcm2835_peripheral gpio = {GPIO_BASE};

// Some forward declarations...
int map_peripheral(struct bcm2835_peripheral *p);
void unmap_peripheral(struct bcm2835_peripheral *p);

int gpio_state = -1;

////////////////
//  main()
////////////////
int main(int argc, char *argv[]) {

    if(argc == 2) {
        if(!strcmp(argv[1], "on"))
            gpio_state = 1;
        if(!strcmp(argv[1], "off"))
            gpio_state = 0;
    }
    if(map_peripheral(&gpio) == -1) {
        printf("Failed to map the physical GPIO registers into the virtual memory space.\n");
        return -1;
    }
    GPFSEL1 &= ~(7 << 21); // Mask out bits 23-21 of GPFSEL1 (i.e. force to zero)
    GPFSEL1 |= (1 << 21);  // Set bits 23-21 of GPFSEL1 to binary '001'

    if(gpio_state == 0)
        GPCLR0 = BIT_7;
    else if(gpio_state == 1)
        GPSET0 = BIT_7;

    usleep(1);    // Delay to allow any change in state to be reflected in the LEVn, register bit.
    printf("GPIO 7 is %s\n", (GPLEV0 & BIT_7) ? "high" : "low");
    unmap_peripheral(&gpio);
}

// Exposes the physical address defined in the passed structure using mmap on /dev/mem
int map_peripheral(struct bcm2835_peripheral *p)
{
   // Open /dev/mem
   if ((p->mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
      printf("Failed to open /dev/mem, try checking permissions.\n");
      return -1;
   }
   p->map = mmap(
      NULL,
      BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED,
      p->mem_fd,  // File descriptor to physical memory virtual file '/dev/mem'
      p->addr_p      // Address in physical map that we want this memory block to expose
   );
   if (p->map == MAP_FAILED) {
        perror("mmap");
        return -1;
   }
   p->addr = (volatile unsigned int *)p->map;
   return 0;
}
void unmap_peripheral(struct bcm2835_peripheral *p) {
    munmap(p->map, BLOCK_SIZE);
    close(p->mem_fd);
}


Finally, la pièce de résistance: there's also a RESTAPI provided with WebIOPi that you can use to turn on and off GPIO ports - here is how you turn on a port using the curl interface to issue REST POSTs from a Mac:

curl -X POST http://192.168.1.210:8000/GPIO/7/function/out
curl -X POST http://192.168.1.210:8000/GPIO/7/value/1

Even easier than the web interface! As with anything on the Pi, there are a dozen ways to do it! I'll probably use the RESTAPI method with a cron schedule on the Mac to turn lights on and off when we're away.

When the RPi model A's come out, I expect I'll use a couple of them to build some more of these.  Pretty simple and useful tools!

Next time: packaging and wrap up (still waiting for the box to put it in...)

1 comment:

  1. AWESOME
    This is exactly what I was looking for. I tried your code but it's not working.
    I spent the entire afternoon yesterday but with no success. I'll give a try also today/tomorrow and I'll come back with some feedback.
    Anyway, thanks for your detailed and very useful explanation.

    Emanuele

    ReplyDelete