summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominic Radermacher <dominic.radermacher@gmail.com>2016-12-24 09:52:28 +0100
committerDominic Radermacher <dominic.radermacher@gmail.com>2016-12-24 09:52:28 +0100
commit1545a3bb49c6ecf462f798a71c3e371427c2ef8c (patch)
tree7754d3ccba7d1b2a46236b5909e1d61989a81256
initial commitv1.2
-rw-r--r--Makefile41
-rw-r--r--data/buttons.xml12
-rw-r--r--include/config.h4
-rw-r--r--include/socket.h3
-rw-r--r--src/mpd-i2c-ctrl.c325
-rw-r--r--src/socket.c100
6 files changed, 485 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ded5273
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,41 @@
+# Update 2011-10-21: object file(s) must come before LDFLAGS and -llibs ?!
+# gcc version used is 4.6.1
+# I think it's time to move to autotools :-(
+# $@ = output file, $< = first input, $+ = all input files
+
+ECHO = /bin/echo -e
+SHELL = /bin/sh
+RM = /bin/rm -f
+
+CC = gcc
+STRIP = strip
+CFLAGS = -g -Os -I./include -I/usr/include/libxml2
+CFLAGS += -Wall -Wstrict-prototypes -Wmissing-prototypes -Wshadow -Wextra
+CFLAGS += -Wunused -Wconversion
+
+PROGS = mpd-i2c-ctrl
+
+all: $(PROGS)
+
+clean:
+ $(RM) src/*.o
+
+tidy:
+ $(RM) src/*.o $(PROGS)
+
+install: $(PROGS)
+ @printf "\t==> Installing progs\n"
+ @install -m 0755 -d $(DESTDIR)/usr/bin
+ @install -m 0755 -t $(DESTDIR)/usr/bin $(PROGS)
+ @ln -s $(DESTDIR)/usr/bin/mpd-i2c-ctrl $(DESTDIR)/usr/bin/mpd-send-cmd
+
+# Generic instructions. Dont change.
+src/%.o: src/%.c
+ @printf "\t--> Compiling `basename $<`\n"
+ @$(CC) $(CFLAGS) -c $< -o $@
+
+# Specific programs
+mpd-i2c-ctrl: src/mpd-i2c-ctrl.o src/socket.o
+ @printf "\t==> Linking objects to output file $@\n"
+ @$(CC) $(CFLAGS) $(LDFLAGS) $+ -lxml2 -o $@
+ @$(STRIP) $@
diff --git a/data/buttons.xml b/data/buttons.xml
new file mode 100644
index 0000000..ac8d184
--- /dev/null
+++ b/data/buttons.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<mpd-i2c-ctrl version="1.0">
+<key id="0d" action="cmd play" />
+<key id="0e" action="cmd stop" />
+<key id="0f" action="cmd next" />
+<key id="10" action="cmd previous" />
+<key id="08" action="playlist empty.m3u" />
+<key id="05" action="playlist uzic.m3u" />
+<key id="06" action="playlist swissclassic.m3u" />
+<key id="07" action="playlist swissjazz.m3u" />
+<key id="01" action="playlist argovia.m3u" />
+</mpd-i2c-ctrl>
diff --git a/include/config.h b/include/config.h
new file mode 100644
index 0000000..296884e
--- /dev/null
+++ b/include/config.h
@@ -0,0 +1,4 @@
+
+#define CONFIG_FILE "/srv/mpd/button.conf"
+
+#define VERSION "1.2"
diff --git a/include/socket.h b/include/socket.h
new file mode 100644
index 0000000..6f9dcab
--- /dev/null
+++ b/include/socket.h
@@ -0,0 +1,3 @@
+int sock_connect(const char *host, const char *port);
+int sock_write(int sd, const char *buf, size_t len);
+int sock_readln(int sd, char *buf, size_t len);
diff --git a/src/mpd-i2c-ctrl.c b/src/mpd-i2c-ctrl.c
new file mode 100644
index 0000000..cc7468a
--- /dev/null
+++ b/src/mpd-i2c-ctrl.c
@@ -0,0 +1,325 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h> /* strchr() */
+#include <unistd.h> /* close() */
+#include <stropts.h> /* ioctl() */
+#include <sys/types.h> /* AF_INET, SOCK_STREAM */
+#include <sys/socket.h> /* socket() */
+#include <arpa/inet.h> /* htons(), hotnl() */
+#include <netdb.h> /* gethostbyname(), getaddrinfo() */
+#include <fcntl.h> /* open(), O_RDWR */
+#include <time.h> /* nanosleep() */
+#include <limits.h>
+#include <sys/inotify.h> /* inotify */
+#include <sys/select.h> /* pselect() */
+#include <linux/i2c-dev.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+
+/* gcc libxml-example.c -I/usr/include/libxml2 -lxml2 */
+
+#include "config.h"
+#include "socket.h"
+
+/* we don't put the following to the config file so users can't break it */
+#define DEVICE "/dev/i2c-1"
+#define MCP23017_ADDR 0x24
+
+#define IPOL_A 0x02
+#define IPOL_B 0x03
+#define GPINTEN_A 0x04
+#define GPINTEN_B 0x05
+#define GPPU_A 0x0C
+#define GPPU_B 0x0D
+#define IOCON_A 0x0A
+#define IOCON_B 0x0B
+#define INTF_A 0x0E
+#define INTF_B 0x0F
+#define INTCON_A 0x08
+#define INTCON_B 0x09
+#define GPIO_A 0x12
+#define GPIO_B 0x13
+
+static const char *basename(const char *s);
+char *buttonlist_get(int key);
+void buttonlist_set(int key, const char *action);
+int i2c_wr(int fd, const uint8_t *data, ssize_t len);
+int mcp23017_init(int fd, int addr);
+int mcp23017_getintf(int fd);
+int mcp23017_waitzero(int fd);
+int mpd_send_cmd(int sd, const char *cmd);
+int mpd_playlist(int sd, const char *playlist);
+int decode_key(int keycode);
+int handle_keypress(uint8_t key);
+int handle_inotify_event(int fd);
+
+/* GLOBAL VARIABLES */
+char *buttonlist[256]; /* can't have more than 128 buttons via I2C */
+
+static const char *basename(const char *s)
+{
+ const char *p;
+
+ for (p=s; p != NULL; ) {
+ if ((p=strchr(s, '/')) != NULL)
+ s=++p;
+ }
+ return s;
+}
+
+char *buttonlist_get(int key)
+{
+ return buttonlist[key];
+}
+
+void buttonlist_set(int key, const char *action)
+{
+ if (buttonlist[key] != NULL) {
+ free(buttonlist[key]);
+ }
+ buttonlist[key]=strdup(action);
+}
+
+int i2c_wr(int fd, const uint8_t *data, ssize_t len)
+{
+ if (write(fd, data, len) != len) {
+ return -1;
+ }
+ return 0;
+}
+
+int mcp23017_init(int fd, int addr)
+{
+ uint8_t buf[4];
+
+ if (ioctl(fd, I2C_SLAVE, addr) < 0) {
+ fprintf(stderr, "can't assign address %02x to I2C\n", addr);
+ return -1;
+ }
+ buf[0]=IPOL_A;
+ buf[1]=0xff;
+ buf[2]=0xff;
+ i2c_wr(fd, buf, 3); /* invert all pins */
+ buf[0]=GPPU_A;
+ buf[1]=0xff;
+ buf[2]=0xff;
+ i2c_wr(fd, buf, 3); /* enable pullups all pins */
+ buf[0]=GPINTEN_A;
+ buf[1]=0xff;
+ buf[2]=0xff;
+ i2c_wr(fd, buf, 3); /* enable interrupt on all pins */
+ buf[0]=INTCON_A;
+ buf[1]=0xff;
+ buf[2]=0xff;
+ i2c_wr(fd, buf, 3); /* interrupt if different from DEFVAL */
+ return 0;
+}
+
+int mcp23017_getintf(int fd)
+{
+ uint8_t buf[2];
+
+ buf[0]=INTF_A;
+ i2c_wr(fd, buf, 1);
+ if (read(fd, buf, 2) != 2) {
+ return -1;
+ }
+ return (buf[1]<<8)+buf[0];
+}
+
+int mcp23017_waitzero(int fd)
+{
+ uint8_t buf[2]={0xff,0xff};
+
+ while ((buf[0] != 0) || (buf[1] != 0)) {
+ buf[0]=GPIO_A; /* reading GPIO also clears interrupt */
+ i2c_wr(fd, buf, 1);
+ if (read(fd, buf, 2) != 2) {
+ fprintf(stderr, "i2c read failed\n");
+ }
+ }
+ return 0;
+}
+
+int mpd_send_cmd(int sd, const char *cmd)
+{
+ char buf[1024];
+
+ snprintf(buf, sizeof(buf), "%s\n", cmd);
+ sock_write(sd, buf, strlen(buf));
+ sock_readln(sd, buf, sizeof(buf));
+ if (strncmp(buf, "OK", 2) != 0) {
+ fprintf(stderr, "mpd cmd failed > '%s'\n", buf);
+ return -1;
+ }
+ return 0;
+}
+
+int mpd_playlist(int sd, const char *playlist)
+{
+ char buf[1024];
+
+ if (mpd_send_cmd(sd, "clear") < 0) {
+ return -1;
+ }
+ snprintf(buf, sizeof(buf), "load %s", playlist);
+ if (mpd_send_cmd(sd, buf) < 0) {
+ return -1;
+ }
+ if (mpd_send_cmd(sd, "play") < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+int decode_key(int keycode)
+{
+ return (sizeof(int)*8)-__builtin_clz(keycode)-1;
+}
+
+int handle_keypress(uint8_t key)
+{
+ int sd, ret=-1;
+ char *action;
+
+ action=buttonlist_get(key);
+ if (action == NULL) {
+ fprintf(stderr, "key %02x is undefined\n", key);
+ return -1;
+ }
+ if ((sd=sock_connect("localhost", "6600")) < 0) {
+ return -1;
+ }
+ if (strncmp(action, "cmd ", 4) == 0) {
+ ret=mpd_send_cmd(sd, &action[4]);
+ } else if (strncmp(action, "playlist ", 9) == 0) {
+ ret=mpd_playlist(sd, &action[9]);
+ } else {
+ fprintf(stderr, "invalid action '%s' for key %02x\n", action, key);
+ }
+ close(sd);
+ return ret;
+}
+
+int handle_inotify_event(int fd)
+{
+#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))
+ char buf[BUF_LEN] __attribute__ ((aligned(8)));
+ ssize_t numRead;
+ struct inotify_event *event;
+
+ if ((numRead = read(fd, buf, BUF_LEN)) <= 0) {
+ return -1;
+ }
+// fprintf(stderr, "Read %ld bytes from inotify fd\n", (long) numRead);
+ /* Process all of the events in buffer returned by read() */
+ for (char *p = buf; p < buf + numRead; ) {
+ event = (struct inotify_event *) p;
+// displayInotifyEvent(event);
+ p += sizeof(struct inotify_event) + event->len;
+ }
+ return 0;
+}
+
+static int parse_config(const char *file)
+{
+ xmlDocPtr doc;
+ xmlNodePtr cur;
+ xmlChar *key, *action;
+ char *err;
+
+ if ((doc = xmlParseFile(file)) == NULL ) {
+ fprintf(stderr,"config not parsed successfully.\n");
+ return -1;
+ }
+ if ((cur = xmlDocGetRootElement(doc)) == NULL) {
+ fprintf(stderr,"empty config file\n");
+ xmlFreeDoc(doc);
+ return -1;
+ }
+ if (xmlStrcmp(cur->name, (const xmlChar *) "mpd-i2c-ctrl")) {
+ fprintf(stderr,"invalid config file\n");
+ xmlFreeDoc(doc);
+ return -1;
+ }
+ for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
+ if ((!xmlStrcmp(cur->name, (const xmlChar *)"key"))) {
+ key = xmlGetProp(cur, (const xmlChar *)"id");
+ if ((key == NULL) || (!*key)) {
+ xmlFree(key);
+ continue;
+ }
+ int val=strtol((char *)key, &err, 16);
+ if ((!*err) && (val >= 0) && (val < 256)) {
+ action = xmlGetProp(cur, (const xmlChar *)"action");
+ buttonlist_set(val, (char *)action);
+ xmlFree(action);
+ }
+ xmlFree(key);
+ } else if (xmlStrcmp(cur->name, (const xmlChar *)"text")) {
+ printf("unknown tag %s\n", cur->name);
+ }
+ }
+ xmlFreeDoc(doc);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int fd, inotifyFd, wd, ret;
+ fd_set fds;
+ struct timespec to;
+
+ const char *_basename=basename(argv[0]);
+ if (strcmp(_basename, "mpd-send-cmd") == 0) {
+ if (argc != 2) {
+ printf("usage: %s <command>\n", argv[0]);
+ return 1;
+ }
+ if ((fd=sock_connect("localhost", "6600")) < 0) {
+ return 1;
+ }
+ mpd_send_cmd(fd, argv[1]);
+ close(fd);
+ }
+ parse_config(CONFIG_FILE);
+ if ((inotifyFd = inotify_init()) == -1) {
+ fprintf(stderr, "inotify_init failed");
+ return 1;
+ }
+ wd = inotify_add_watch(inotifyFd, CONFIG_FILE, IN_MODIFY);
+ if (wd == -1) {
+ fprintf(stderr, "inotify_add_watch failed");
+ }
+// printf("Watching %s file using wd %d\n", CONFIG_FILE, wd);
+ if ((fd=open(DEVICE, O_RDWR)) < 0) {
+ fprintf(stderr, "can't open '%s'\n", DEVICE);
+ return 1;
+ }
+ daemon(0,0);
+ for (;;) {
+ mcp23017_init(fd, MCP23017_ADDR);
+ int key=mcp23017_getintf(fd);
+ if (key > 0) {
+ int code=decode_key(key);
+// printf("got key %02x => decoded = %04x\n", key, code);
+ handle_keypress(code);
+ mcp23017_waitzero(fd); /* wait until keys released, clr irq */
+ }
+ to.tv_sec=0;
+ to.tv_nsec=200000000; /* 0.2 sec */
+ FD_ZERO(&fds);
+ FD_SET(inotifyFd, &fds); /* 0 = stdin */
+ ret=pselect(inotifyFd+1, &fds, NULL, NULL, &to, NULL);
+ if (ret > 0) {
+ if (FD_ISSET(inotifyFd, &fds)) {
+ handle_inotify_event(inotifyFd);
+ fprintf(stderr, "config changed, re-reading it\n");
+ parse_config(CONFIG_FILE);
+ }
+ } else if (ret < 0) {
+ fprintf(stderr, "pselect failed\n");
+ }
+ }
+ return 0;
+}
diff --git a/src/socket.c b/src/socket.c
new file mode 100644
index 0000000..2ad5f41
--- /dev/null
+++ b/src/socket.c
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h> /* close() */
+#include <sys/types.h> /* AF_INET, SOCK_STREAM */
+#include <sys/socket.h> /* socket() */
+#include <arpa/inet.h> /* htons(), hotnl() */
+#include <netdb.h> /* gethostbyname(), getaddrinfo() */
+
+#include "socket.h"
+
+int sock_connect(const char *host, const char *port)
+{
+ int sd, ret;
+// struct sockaddr_in *sa;
+ struct addrinfo *ai, *ptr;
+ struct addrinfo hint;
+
+ if ((host == NULL) || (*host == 0)) {
+ return -1;
+ }
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV;
+ hint.ai_family = AF_INET6;
+ hint.ai_socktype = SOCK_STREAM;
+ if ((ret=getaddrinfo(host, port, &hint, &ai)) != 0) {
+ printf("%s: getaddrinfo returned %i\n", __FUNCTION__, ret);
+ return -1;
+ }
+ for (ptr=ai; ptr != NULL; ptr=ptr->ai_next) {
+ if ((sd=socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1) {
+ continue;
+ }
+ if (connect(sd, ptr->ai_addr, ptr->ai_addrlen) == -1) {
+ close(sd);
+ continue;
+ }
+// sa=(struct sockaddr_in *)ptr->ai_addr;
+ break; /* if we get here, we have connected successfully */
+ }
+ if (ptr == NULL) { /* end reached with no connect */
+ return -1;
+ }
+ freeaddrinfo(ai);
+ return sd;
+}
+
+/* --------------------------------------------------------------------
+ Write to a socket
+ -------------------------------------------------------------------- */
+int sock_write(int sd, const char *buf, size_t len)
+{
+ ssize_t n;
+ size_t wrlen = 0;
+
+ while (len) {
+ n = write(sd, buf, len);
+ if (n <= 0)
+ return -1;
+ len -= n;
+ wrlen += n;
+ buf += n;
+ }
+ return wrlen;
+}
+
+/* --------------------------------------------------------------------
+ Read a \n terminated line from a socket
+ -------------------------------------------------------------------- */
+
+int sock_readln(int sd, char *buf, size_t len)
+{
+ char *newline, *bp = buf;
+ ssize_t n;
+
+ if (--len < 1) {
+ return -1;
+ }
+ do {
+ /*
+ * The reason for these gymnastics is that we want two things:
+ * (1) to read \n-terminated lines,
+ * (2) to return the true length of data read, even if the
+ * data coming in has embedded NULs.
+ */
+ if ((n = recv(sd, bp, len, MSG_PEEK)) <= 0) {
+ return -1;
+ }
+ if ((newline = memchr(bp, '\n', n)) != NULL) {
+ n = newline - bp + 1;
+ }
+ if ((n = read(sd, bp, n)) == -1) {
+ return -1;
+ }
+ bp += n;
+ len -= n;
+ } while (!newline && len);
+ *bp = '\0';
+ return bp - buf;
+}