diff options
author | Dominic Radermacher <dominic.radermacher@gmail.com> | 2016-12-24 09:52:28 +0100 |
---|---|---|
committer | Dominic Radermacher <dominic.radermacher@gmail.com> | 2016-12-24 09:52:28 +0100 |
commit | 1545a3bb49c6ecf462f798a71c3e371427c2ef8c (patch) | |
tree | 7754d3ccba7d1b2a46236b5909e1d61989a81256 |
initial commitv1.2
-rw-r--r-- | Makefile | 41 | ||||
-rw-r--r-- | data/buttons.xml | 12 | ||||
-rw-r--r-- | include/config.h | 4 | ||||
-rw-r--r-- | include/socket.h | 3 | ||||
-rw-r--r-- | src/mpd-i2c-ctrl.c | 325 | ||||
-rw-r--r-- | src/socket.c | 100 |
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; +} |