summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Kuzminsky <seb@highlab.com>2011-01-12 23:59:59 -0700
committerSebastian Kuzminsky <seb@highlab.com>2011-01-13 11:32:23 -0700
commitc6118997d8d98ae52fd73d0d016142c5981986a4 (patch)
treeb843fdcb53b7b9f18c4c9f6a17adf4b187990110
parent9dcbf97dea9ca7c2761efe4ae424754d0c5ea83e (diff)
downloadlinuxcnc-c6118997d8d98ae52fd73d0d016142c5981986a4.tar.gz
linuxcnc-c6118997d8d98ae52fd73d0d016142c5981986a4.zip
add a driver for the Contour Design ShuttleXpress
-rw-r--r--docs/man/man1/shuttlexpress.141
-rw-r--r--src/hal/user_comps/Submakefile9
-rw-r--r--src/hal/user_comps/shuttlexpress.c359
3 files changed, 409 insertions, 0 deletions
diff --git a/docs/man/man1/shuttlexpress.1 b/docs/man/man1/shuttlexpress.1
new file mode 100644
index 000000000..fa76db70c
--- /dev/null
+++ b/docs/man/man1/shuttlexpress.1
@@ -0,0 +1,41 @@
+.TH SHUTTLEXPRESS "1" "2011-01-13" "EMC Documentation" "HAL User's Manual"
+.de TQ
+.br
+.ns
+.TP \\$1
+..
+.SH NAME
+shuttlexpress \- control HAL pins with the ShuttleXpress device made by Contour Design
+.SH SYNOPSIS
+\fIloadusr\fR \fBshuttlexpress\fR \fI[DEVICE ...]\fR
+.SH DESCRIPTION
+shuttlexpress is a userspace HAL component that interfaces Contour Design's
+ShuttleXpress device with EMC2's HAL. The ShuttleXpress has five momentary
+buttons, a 10 count/revolution jog wheel with detents, and a 15-position
+spring-loaded outer wheel that returns to center when released.
+.PP
+If it is started without command-line arguments, it will probe all
+/dev/hidraw* device files for ShuttleXpress devices, and use all devices
+found. If it is started with command-line arguments, only will only
+probe the devices specified.
+.SH UDEV
+The shuttlexpress module needs read permission on the /dev/hidraw*
+device files. This can be accomplished by adding a file
+\fB/etc/udev/rules.d/99-shuttlexpress.rules\fR, with the following contents:
+
+SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0b33", ATTRS{idProduct}=="0020", MODE="0444"
+
+.SH A warning about the Jog Wheel
+The ShuttleXpress device has an internal 8-bit counter for the current
+jog-wheel position. The shuttlexpress driver can not know this value
+until the ShuttleXpress device sends its first event. When the first
+event comes into the driver, the driver uses the device's then-current
+jog-wheel count as 0.
+
+This means that if the first event is generated by a jog-wheel move,
+that first move will be lost.
+
+Any user interaction with the ShuttleXpress device will generate an event,
+informing the driver of the jog-wheel position. So if you (for example)
+push one of the buttons at startup, the jog-wheel will work fine and
+notice the first click.
diff --git a/src/hal/user_comps/Submakefile b/src/hal/user_comps/Submakefile
index 16bfeec08..dd848dca7 100644
--- a/src/hal/user_comps/Submakefile
+++ b/src/hal/user_comps/Submakefile
@@ -14,13 +14,22 @@ MODBUSLDFLAGS := $(GLIB_LIBS)
MODBUSCCFLAGS := $(GLIB_CFLAGS)
USERSRCS += $(MODBUSSRCS)
+SHUTTLEXPRESS_SRC = hal/user_comps/shuttlexpress.c
+USERSRCS += $(SHUTTLEXPRESS_SRC)
+
$(call TOOBJSDEPS, $(MODBUSSRCS)) : EXTRAFLAGS += $(MODBUSCCFLAGS)
+ $(call TOOBJSDEPS, $(SHUTTLEXPRESS_SRC)) :
../bin/gs2_vfd: $(call TOOBJS, $(MODBUSSRCS)) ../lib/libemchal.so.0
$(ECHO) Linking $(notdir $@)
@$(CC) $(LDFLAGS) $(MODBUSLDFLAGS) -o $@ $^
TARGETS += ../bin/gs2_vfd
+../bin/shuttlexpress: $(call TOOBJS, $(SHUTTLEXPRESS_SRC)) ../lib/libemchal.so.0
+ $(ECHO) Linking $(notdir $@)
+ @$(CC) $(LDFLAGS) -o $@ $^
+TARGETS += ../bin/shuttlexpress
+
../include/%.h: ./hal/user_comps/%.h
cp $^ $@
../include/%.hh: ./hal/user_comps/%.hh
diff --git a/src/hal/user_comps/shuttlexpress.c b/src/hal/user_comps/shuttlexpress.c
new file mode 100644
index 000000000..7956ae6de
--- /dev/null
+++ b/src/hal/user_comps/shuttlexpress.c
@@ -0,0 +1,359 @@
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <linux/hidraw.h>
+
+#include "hal.h"
+
+
+
+
+#define Max(a, b) ((a) > (b) ? (a) : (b))
+
+
+
+
+// USB Vendor and Product IDs
+#define VENDOR_ID 0x0b33 // Contour Design
+#define PRODUCT_ID 0x0020 // ShuttleXpress
+
+
+// each packet from the ShuttleXpress is this many bytes
+#define PACKET_LEN 5
+
+
+// the module name, and prefix for all HAL pins
+char *modname = "shuttlexpress";
+
+
+int hal_comp_id;
+
+
+
+
+// each ShuttleXpress presents this interface to HAL
+struct shuttlexpress_hal {
+ hal_bit_t *button_0;
+ hal_bit_t *button_0_not;
+ hal_bit_t *button_1;
+ hal_bit_t *button_1_not;
+ hal_bit_t *button_2;
+ hal_bit_t *button_2_not;
+ hal_bit_t *button_3;
+ hal_bit_t *button_3_not;
+ hal_bit_t *button_4;
+ hal_bit_t *button_4_not;
+ hal_s32_t *count; // accumulated counts from the jog wheel
+ hal_s32_t *rotation; // current position of the springy outer wheel (FIXME: for lack of a better name...)
+};
+
+
+struct shuttlexpress {
+ int fd;
+ char *device_file;
+ struct shuttlexpress_hal *hal;
+ int read_first_event;
+ int prev_count;
+};
+
+
+// this will become an array of all the ShuttleXpress devices we're using
+struct shuttlexpress **shuttlexpress = NULL;
+int num_devices = 0;
+
+
+
+
+static void exit_handler(int sig) {
+ printf("%s: exiting\n", modname);
+ exit(0);
+}
+
+
+static void call_hal_exit(void) {
+ hal_exit(hal_comp_id);
+}
+
+
+
+
+int read_update(struct shuttlexpress *s) {
+ int r;
+ int8_t packet[PACKET_LEN];
+
+ r = read(s->fd, packet, PACKET_LEN);
+ if (r < 0) {
+ fprintf(stderr, "%s: error reading %s: %s\n", modname, s->device_file, strerror(errno));
+ return -1;
+ } else if (r == 0) {
+ fprintf(stderr, "%s: EOF on %s\n", modname, s->device_file);
+ return -1;
+ }
+
+ *s->hal->button_0 = packet[3] & 0x10;
+ *s->hal->button_0_not = !*s->hal->button_0;
+ *s->hal->button_1 = packet[3] & 0x20;
+ *s->hal->button_1_not = !*s->hal->button_1;
+ *s->hal->button_2 = packet[3] & 0x40;
+ *s->hal->button_2_not = !*s->hal->button_2;
+ *s->hal->button_3 = packet[3] & 0x80;
+ *s->hal->button_3_not = !*s->hal->button_3;
+ *s->hal->button_4 = packet[4] & 0x01;
+ *s->hal->button_4_not = !*s->hal->button_4;
+
+ {
+ int curr_count = packet[1];
+
+ if (s->read_first_event == 0) {
+ *s->hal->count = 0;
+ s->prev_count = curr_count;
+ s->read_first_event = 1;
+ } else {
+ int diff_count = curr_count - s->prev_count;
+ if (diff_count > 128) diff_count -= 256;
+ if (diff_count < -128) diff_count += 256;
+ *s->hal->count += diff_count;
+ s->prev_count = curr_count;
+ }
+ }
+
+ *s->hal->rotation = packet[0];
+
+ return 0;
+}
+
+
+struct shuttlexpress *check_for_shuttlexpress(char *dev_filename) {
+ struct shuttlexpress *s;
+ struct hidraw_devinfo devinfo;
+ char name[100];
+ int r;
+
+ printf("%s: checking %s\n", modname, dev_filename);
+
+ s = (struct shuttlexpress *)calloc(1, sizeof(struct shuttlexpress));
+ if (s == NULL) {
+ fprintf(stderr, "%s: out of memory!\n", modname);
+ return NULL;
+ }
+
+ s->device_file = dev_filename;
+
+ s->fd = open(s->device_file, O_RDONLY);
+ if (s->fd < 0) {
+ fprintf(stderr, "%s: error opening %s: %s\n", modname, s->device_file, strerror(errno));
+ if (errno == EACCES) {
+ fprintf(stderr, "%s: make sure you have read permission on %s, read the shuttlexpress(1) manpage for more info\n", modname, s->device_file);
+ }
+ goto fail0;
+ }
+
+
+ r = ioctl(s->fd, HIDIOCGRAWINFO, &devinfo);
+ if (r < 0) {
+ fprintf(stderr, "%s: error with ioctl HIDIOCGRAWINFO on %s: %s\n", modname, s->device_file, strerror(errno));
+ goto fail1;
+ }
+
+ if (devinfo.vendor != VENDOR_ID) {
+ fprintf(stderr, "%s: dev %s has unexpected Vendor ID 0x%04x (expected Contour Design, 0x%04x)\n", modname, s->device_file, devinfo.vendor, VENDOR_ID);
+ goto fail1;
+ }
+
+ if (devinfo.product != PRODUCT_ID) {
+ fprintf(stderr, "%s: dev %s has unexpected Product ID 0x%04x (expected ShuttleXpress, 0x%04x)\n", modname, s->device_file, devinfo.product, PRODUCT_ID);
+ goto fail1;
+ }
+
+ r = ioctl(s->fd, HIDIOCGRAWNAME(99), name);
+ if (r < 0) {
+ fprintf(stderr, "%s: error with ioctl HIDIOCGRAWNAME on %s: %s\n", modname, s->device_file, strerror(errno));
+ goto fail1;
+ }
+ printf("%s: found %s on %s\n", modname, name, s->device_file);
+
+
+ s->hal = (struct shuttlexpress_hal *)hal_malloc(sizeof(struct shuttlexpress_hal));
+ if (s->hal == NULL) {
+ fprintf(stderr, "%s: ERROR: unable to allocate HAL shared memory\n", modname);
+ goto fail1;
+ }
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_0), hal_comp_id, "%s.%d.button-0", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_0_not), hal_comp_id, "%s.%d.button-0-not", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_1), hal_comp_id, "%s.%d.button-1", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_1_not), hal_comp_id, "%s.%d.button-1-not", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_2), hal_comp_id, "%s.%d.button-2", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_2_not), hal_comp_id, "%s.%d.button-2-not", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_3), hal_comp_id, "%s.%d.button-3", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_3_not), hal_comp_id, "%s.%d.button-3-not", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_4), hal_comp_id, "%s.%d.button-4", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_bit_newf(HAL_OUT, &(s->hal->button_4_not), hal_comp_id, "%s.%d.button-4-not", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_s32_newf(HAL_OUT, &(s->hal->count), hal_comp_id, "%s.%d.count", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ r = hal_pin_s32_newf(HAL_OUT, &(s->hal->rotation), hal_comp_id, "%s.%d.rotation", modname, num_devices);
+ if (r != 0) goto fail1;
+
+ *s->hal->button_0 = 0;
+ *s->hal->button_0_not = 1;
+ *s->hal->button_1 = 0;
+ *s->hal->button_1_not = 1;
+ *s->hal->button_2 = 0;
+ *s->hal->button_2_not = 1;
+ *s->hal->button_3 = 0;
+ *s->hal->button_3_not = 1;
+ *s->hal->button_4 = 0;
+ *s->hal->button_4_not = 1;
+ *s->hal->count = 0;
+ *s->hal->rotation = 0;
+
+ return s;
+
+
+fail1:
+ close(s->fd);
+
+fail0:
+ free(s);
+ return NULL;
+}
+
+
+
+
+int main(int argc, char *argv[]) {
+ int i;
+ glob_t glob_buffer;
+
+ char **names;
+ int num_names;
+
+
+ hal_comp_id = hal_init(modname);
+ if (hal_comp_id < 1) {
+ fprintf(stderr, "%s: ERROR: hal_init failed\n", modname);
+ exit(1);
+ }
+
+ signal(SIGINT, exit_handler);
+ signal(SIGTERM, exit_handler);
+ atexit(call_hal_exit);
+
+
+ // get the list of device filenames to check for ShuttleXpress devices
+ if (argc > 1) {
+ // list of devices provided on the command line
+ names = &argv[1];
+ num_names = argc - 1;
+ } else {
+ // probe for /dev/hidraw*
+ int r;
+
+ r = glob("/dev/hidraw*", 0, NULL, &glob_buffer);
+ if (r == GLOB_NOMATCH) {
+ fprintf(stderr, "%s: no /dev/hidraw* found, is device plugged in?\n", modname);
+ exit(1);
+ } else if (r != 0) {
+ fprintf(stderr, "%s: error with glob!\n", modname);
+ exit(1);
+ }
+ names = glob_buffer.gl_pathv;
+ num_names = glob_buffer.gl_pathc;
+
+ // the pathnames we got from glob(3) are used in the shuttlexpress array, so we intentionally dont call globfree(3)
+ }
+
+
+ // probe for ShuttleXpress devices on all those device file names
+ for (i = 0; i < num_names; i ++) {
+ struct shuttlexpress *s;
+ s = check_for_shuttlexpress(names[i]);
+ if (s == NULL) continue;
+
+ num_devices ++;
+ shuttlexpress = (struct shuttlexpress **)realloc(shuttlexpress, (num_devices * sizeof(struct shuttlexpress *)));
+ if (shuttlexpress == NULL) {
+ fprintf(stderr, "%s: out of memory!\n", modname);
+ exit(1);
+ }
+ shuttlexpress[num_devices - 1] = s;
+ }
+
+
+ if (num_devices == 0) {
+ fprintf(stderr, "%s: no devices found\n", modname);
+ exit(1);
+ }
+
+
+ hal_ready(hal_comp_id);
+
+
+ // select on all the hidraw devices, process events from the active ones
+ while (1) {
+ fd_set readers;
+ int max_fd;
+ int i;
+ int r;
+
+ FD_ZERO(&readers);
+ max_fd = -1;
+
+ for (i = 0; i < num_devices; i ++) {
+ FD_SET(shuttlexpress[i]->fd, &readers);
+ max_fd = Max(max_fd, shuttlexpress[i]->fd);
+ }
+
+ r = select(max_fd + 1, &readers, NULL, NULL, NULL);
+ if (r < 0) {
+ if ((errno == EAGAIN) || (errno == EINTR)) continue;
+ fprintf(stderr, "%s: error with select!\n", modname);
+ exit(1);
+ }
+
+ for (i = 0; i < num_devices; i ++) {
+ if (FD_ISSET(shuttlexpress[i]->fd, &readers)) {
+ r = read_update(shuttlexpress[i]);
+ if (r < 0) {
+ exit(1);
+ }
+ }
+ }
+ }
+
+ exit(0);
+}
+