Labels

lego (12) Linux (39) pi (20) Thinkpads (4)

Saturday, 21 December 2013

Using the USB Big Red Button / Panic Button on the Raspberry Pi [day 10 of 20-days-of-posts series]

As I had allured to in a previous post, I had purchased a Panic/ Stress relief USB button from a local store.  Full specifications available at their manufacturer website.  I believe its available on the DX website as well, or at least a clone of it.



It features essentially a USB interface to 2 i/o streams that would be either high or low.   It has two input states -- one for the case cover being lifted and another for the button being pressed.




Taking apart the unit is fairly easy.  There are only 4 Philips screws.

As expected, inside we can see the button presses down on a standard button attached to a logic board.


The yellow cable leads to a set of electrical contacts that almost resemble tweezers.  This tells the logic board whether the case lid is open.


It's hard to see on a photo, but on the case, there is a leverage switch that pops out when the lid is open, which releases the "tweezers" contacts, breaking the current, which generates a 0 on the input.



That's all there is to the internals.  The components can be removed and setup in your own case.  If you use lego, it would be possible to create a button to press down on the switch, and some blocks to press or depress the electrical contacts for the lid switch.

My goal originally was to create a switch that could be used to shutdown the Raspberry Pi.  In actuality, it can be used for a multitude of purposes.

Pros:
  • cheap and resourceful
  • kind of geeky (re-pursing a device)
  • the casing can be taken apart and just the logical board with usb cable can be deployed

Cons:
  • a USB device that occupies a USB port


For the logic part, there are a few options I've found.  There is an open-source ruby gems implementation worth playing around with for fun:
https://github.com/derrick/dream_cheeky

I've ended up using a device driver written in C.  Malcolm Sparks provided a great device driver for this device on his blog:

http://blog.opensensors.io/blog/2013/11/25/the-big-red-button/

Malcolm goes into detail of pertaining to Arch Linux which didn't work as smooth for Debian.  I'll simply the procedure to follow below for the Raspberry Pi.

Three easy steps to follow.

First, test the device.  Plug in the device on the USB port.  Run dmesg to get the device information.

I saw something like this in dmesg output:


hid-generic 0003:1D34:000D.0004: hiddev0,hidraw0: USB HID v1.10 Device [Dream Link DL100B Dream Cheeky Generic Controller] on usb-0000:00:1d.0-1/input0

The device name is in that output.  In this case it is hiddev0. Check /dev for the existence of /dev/hidraw0.  You will need to change the permissions of /dev/hidraw0 if you won't be using the root user to run your script to interact with the buttons.  You can change the permissions by running sudo chmod 666 /dev/hidraw0. If the script runs under root, this is not necessary.  But it is necessary if you want to test out the switch with a non-root user.

Second, build the device driver.

Slightly modified version of Malcolm Sparks script, for the Raspberry Pi -- note the changes in BOLD, including changing the /dev/big-red-button to the actual device location (such as /dev/hidraw0).
(download big-red-button.c)

/*
,* Copyright © 2013, Malcolm Sparks <malcolm@congreve.com>. All Rights Reserved.
,*
,* A program to convert USB firing events from the Dream Cheeky 'Big Red Button' to MQTT events.
,*/

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define LID_CLOSED 21
#define BUTTON_PRESSED 22
#define LID_OPEN 23

int main(int argc, char **argv)
{
  int fd;
  int i, res, desc_size = 0;
  char buf[256];

  /* Use a udev rule to make this device */
  fd = open("/dev/hidraw0", O_RDWR|O_NONBLOCK);

  if (fd < 0) {
    perror("Unable to open device");
    return 1;
  }

  int prior = LID_CLOSED;

  while (1) {
    memset(buf, 0x0, sizeof(buf));
    buf[0] = 0x08;
    buf[7] = 0x02;

    res = write(fd, buf, 8);
    if (res < 0) {
      perror("write");
      exit(1);
    }

    memset(buf, 0x0, sizeof(buf));
    res = read(fd, buf, 8);

    if (res >= 0) {
      if (prior == LID_CLOSED && buf[0] == LID_OPEN) {
         printf("Ready to fire!\n");
         system("sudo initctl stop xbmc");
         fflush(stdout);
      } else if (prior != BUTTON_PRESSED && buf[0] == BUTTON_PRESSED) {
         printf("Fire!\n");
         system("shutdown now -h");
         fflush(stdout);
      } else if (prior != LID_CLOSED && buf[0] == LID_CLOSED) {
         printf("Stand down!\n");
         fflush(stdout);
      }
      prior = buf[0];
    }
    usleep(20000); /* Sleep for 20ms*/
  }
}


You can modify the device driver to suit your needs.  There are really three states that can get triggered and be associated with tasks or activities:

  1. lid is raised
  2. lid is closed
  3. button is pressed

In the closed-form of the button, condition 3 can only happen when condition 1 is raised and is not possible when condition 2 is raised.  In a disassembled switch, you could repurpose condition 3 to behave differently based on whether it is condition 1 or 2 that is active (thus, you could add a 4th state in the device driver for buf[0] == LID_OPEN && buf[0] == BUTTON_PRESSED) since the two states can be independent based on your implementation of the physical button and contacts.

We'll use only states 1-3, assuming 3 can only occur when 1 is active.  Edit the behaviour of the device driver, where appropriate:
  • for when the lid is opened... [example showing stop XBMC]
      if (prior == LID_CLOSED && buf[0] == LID_OPEN) {
         printf("Ready to fire!\n");
         system("initctl stop xbmc");
         fflush(stdout);
  • for when the button is pressed...  [example showing shutdown system]     
      } else if (prior != BUTTON_PRESSED && buf[0] == BUTTON_PRESSED) {
    printf("Fire!\n");
    system("shutdown now -h");
    fflush(stdout);

  •  for when the lid is closed (depending it having been opened)...      

      } else if (prior != LID_CLOSED && buf[0] == LID_CLOSED) {
         printf("Stand down!\n");
         fflush(stdout);

Compile the device driver.
cc big-red-button.c -o big-red-button

Third,  install the device driver.

Install the executable by placing it in /etc/big-red-button.  Make sure the +x permission (i.e. sudo chmod 755 /etc/big-red-button) is set.  Add /etc/big-red-button & to the bottom of /etc/rc.local but before the exit.

Now when the Raspberry Pi boots up, if the USB Big Red Button is attached, it should load the device driver created above automatically, awaiting the change in state for the lid and button, taking appropriate action when activated.


3 comments:

  1. Hey, brilliant post.

    I had the same big red button at work but got bored with it, so I brought it home to attach to my cluster. I looked at using it and came across the same code you did, but followed your steps exactly for the ARM architecture and it works, except when I try to get it going "/etc/rc.local but before the exit." It works fine, I edited the c program as needed and compiles perfectly. It also runs well after putting a cronjob to run sudo chmod 666 /dev/hidraw0 as it lost it each time.

    I have put it to /etc/rc.local as

    "#!/bin/sh -e
    #
    # rc.local
    #
    # This script is executed at the end of each multiuser runlevel.
    # Make sure that the script will "exit 0" on success or any other
    # value on error.
    #
    # In order to enable or disable this script just change the execution
    # bits.
    #
    # By default this script does nothing.

    /etc/big-red-button &

    exit 0
    "

    Any ideas? I have tried varitions without the &, on rc.0, on etc/init.d/rc.local and nothing.

    I tried a workaround by using a cron job via crontab -e to start on reboot, but no dice.

    Keep up the good work, I should write up my experiences as well!

    ReplyDelete
  2. Raspbian Wheezey.

    I got the permissions to stay and can get it running via the CLI. I also have it running as a cronjob with sudo bash /script.bash and when I lift the lid and hit it, it reboots the master only. Very odd.

    ReplyDelete