]> www.infradead.org Git - users/dwmw2/mpc-car2pc.git/commitdiff
initial commit
authorDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 20 May 2009 21:39:02 +0000 (22:39 +0100)
committerDavid Woodhouse <David.Woodhouse@intel.com>
Wed, 20 May 2009 21:39:02 +0000 (22:39 +0100)
62-car2pc.rules [new file with mode: 0644]
Makefile [new file with mode: 0644]
mpc-car2pc.c [new file with mode: 0644]

diff --git a/62-car2pc.rules b/62-car2pc.rules
new file mode 100644 (file)
index 0000000..4d59a31
--- /dev/null
@@ -0,0 +1,4 @@
+
+ACTION=="add", SUBSYSTEMS=="usb", ENV{ID_MODEL}=="FT232R_USB_UART", RUN+="/home/dwmw2/git/mpc-car2pc/mpc-car2pc /dev/%k"
+
+ACTION=="add", SUBSYSTEMS=="tty", ENV{ID_MODEL}=="CAR2PC__-__HU", RUN+="/home/dwmw2/git/mpc-car2pc/mpc-car2pc /dev/%k"
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..0933944
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+CFLAGS += $(shell pkg-config --cflags libmpd) -g
+LDFLAGS += $(shell pkg-config --libs libmpd)
+
+all: mpc-car2pc
+
+clean:
+       rm -f mpc-car2pc *.o
diff --git a/mpc-car2pc.c b/mpc-car2pc.c
new file mode 100644 (file)
index 0000000..3ae68c0
--- /dev/null
@@ -0,0 +1,397 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <poll.h>
+#include <stdint.h>
+#include <libmpd/libmpdclient.h>
+
+#define CAR2PC_CMD(a, b) (((a)<<8) | b)
+
+#define CAR2PC_STOP            CAR2PC_CMD('S', 'T')
+#define CAR2PC_PLAY            CAR2PC_CMD('P', 'L')
+#define CAR2PC_PAUSE           CAR2PC_CMD('P', 'A')
+#define CAR2PC_FFWD            CAR2PC_CMD('F', 'R')
+#define CAR2PC_FREW            CAR2PC_CMD('F', 'R')
+#define CAR2PC_NEXT_DISC       CAR2PC_CMD('N', 'D')
+#define CAR2PC_PREV_DISC       CAR2PC_CMD('P', 'D')
+#define CAR2PC_NEXT_TRACK      CAR2PC_CMD('N', 'T')
+#define CAR2PC_PREV_TRACK      CAR2PC_CMD('P', 'T')
+#define CAR2PC_TRACK           CAR2PC_CMD('T', 'R')
+#define CAR2PC_SCAN            CAR2PC_CMD('S', 'C')
+
+#define CAR2PC_MAX_DISC                6
+#define CAR2PC_MAX_TRACK       99
+
+mpd_Connection *mpd;
+int car2pc_fd;
+int hupped;
+int disc = 1;
+
+void handle_hup(int sig)
+{
+       hupped = 1;
+}
+
+char *read_car2pc_event(void)
+{
+       static unsigned char buf[258];
+       static int buf_len = 0;
+       ssize_t rd;
+       int i;
+
+       rd = read(car2pc_fd, buf + buf_len,  sizeof(buf) - buf_len);
+       if (rd < 0) {
+               if (errno == EAGAIN)
+                       return NULL;
+               syslog(LOG_ERR, "car2pc serial read: %s\n", strerror(errno));
+               exit(1);
+       }
+
+       buf_len += rd;
+ again:
+       for (i = 0; i < buf_len; i++)
+               if (buf[i] == 0xFF)
+                       break;
+
+       if (i == buf_len) {
+               buf_len = 0;
+               return NULL;
+       }
+       if (i) {
+               buf_len -= i;
+               memmove(buf, buf + i, buf_len);
+       }
+       if (buf_len >= 2 && buf_len >= 2 + buf[1]) {
+               char *ret = malloc(buf[1] + 1);
+               if (!ret)
+                       return NULL;
+               memcpy(ret, buf + 2, buf[1]);
+               ret[buf[1]] = 0;
+
+               buf_len -= (2 + buf[1]);
+               if (buf_len)
+                       memmove(buf, buf + 2 + buf[1], buf_len);
+               return ret;
+       }
+       return NULL;
+}
+
+int send_car2pc_command(char *cmd, ...)
+{
+       char buf[255];
+
+       va_list args;
+       va_start(args, cmd);
+       buf[0] = 0xff;
+       buf[1] = vsnprintf(buf+2, 253, cmd, args);
+       write(car2pc_fd, buf, buf[1] + 2);
+}
+
+void send_song_info(void)
+{
+       mpd_InfoEntity *e;
+       mpd_sendCurrentSongCommand(mpd);
+       while ((e = mpd_getNextInfoEntity(mpd))) {
+               mpd_Song *s = e->info.song;
+
+               if (e->type == MPD_INFO_ENTITY_TYPE_SONG) {
+                       send_car2pc_command("NM%s", s->title?:s->file?:"unknown");
+                       send_car2pc_command("AL%s", s->album?:"unknown");
+                       send_car2pc_command("AR%s", s->artist?:"unknown");
+               }
+               mpd_freeInfoEntity(e);
+       }
+}
+
+int send_timer(int seconds)
+{
+       int hours = seconds / 3600;
+       int minutes = (seconds / 60) % 60;
+       seconds %= 60;
+
+       send_car2pc_command("TM%02d%02d%02d", hours, minutes, seconds);
+}
+
+void start_playback(void)
+{
+       mpd_sendPlayCommand(mpd, -1);
+       mpd_finishCommand(mpd);
+       if (mpd->error) {
+               syslog(LOG_ERR, "Failed to send 'play' command: %s\n",
+                      mpd->errorStr);
+       }
+}
+
+void change_track(int track, int max)
+{
+       if (track > max)
+               track = max;
+       mpd_sendPlayCommand(mpd, track - 1);
+       mpd_finishCommand(mpd);
+       if (mpd->error) {
+               syslog(LOG_ERR, "Failed to send 'play' command: %s\n",
+                      mpd->errorStr);
+       }
+}
+
+void pause_playback(void)
+{
+       mpd_sendPauseCommand(mpd, 1);
+       mpd_finishCommand(mpd);
+       if (mpd->error) {
+               syslog(LOG_ERR, "Failed to send 'pause' command: %s\n",
+                      mpd->errorStr);
+       }
+}
+void change_disc(int direction)
+{
+       char buf[6];
+       int new_disc = disc;
+
+       for (;;) {
+               new_disc += direction;
+
+               if (new_disc < 1)
+                       new_disc = CAR2PC_MAX_DISC;
+               else if (new_disc > CAR2PC_MAX_DISC)
+                       new_disc = 1;
+
+               mpd_sendClearCommand(mpd);
+               mpd_finishCommand(mpd);
+
+               sprintf(buf, "disc%d", new_disc);
+               mpd_sendLoadCommand(mpd, buf);
+               mpd_finishCommand(mpd);
+               if (!mpd->error) {
+                       disc = new_disc;
+                       syslog(LOG_NOTICE, "Loaded playlist '%s'\n", buf);
+                       send_car2pc_command("DS%03d", disc);
+                       start_playback();
+                       return;
+               }
+               syslog(LOG_ERR, "Load playlist '%s': %s\n", buf,
+                      mpd->errorStr);
+               if (new_disc == disc)
+                       break;
+       }
+}
+
+
+int mainloop(void)
+{
+       int track = -1, plid = -1, last_time = -1;
+       int nr_tracks;
+       int want_state = -1;
+       unsigned char *ev;
+       mpd_Status *sts;
+       struct pollfd pfd = {car2pc_fd, POLLIN, 0};
+
+       while (!hupped) {
+               mpd_sendStatusCommand(mpd);
+               sts = mpd_getStatus(mpd);
+               if (!sts) {
+                       syslog(LOG_ERR, "Failed to get MPD status: %s\n",
+                              mpd->errorStr);
+                       exit(1);
+               }
+               nr_tracks = sts->playlistLength;
+               if (nr_tracks > CAR2PC_MAX_TRACK)
+                       nr_tracks = CAR2PC_MAX_TRACK;
+               if (want_state != -1 && sts->state != want_state) {
+                       if (want_state == MPD_STATUS_STATE_PLAY) {
+                               track = plid = last_time = -1;
+                               start_playback();
+                       } else if (sts->state == MPD_STATUS_STATE_PLAY)
+                               pause_playback();
+                       want_state = -1;
+                       continue;
+               }
+
+               if (sts->state == MPD_STATUS_STATE_PLAY) {
+                       if (sts->song != track ||
+                           sts->playlist != plid) {
+                               track = sts->song;
+                               send_car2pc_command("TR%03d", track + 1);
+                               send_song_info();
+                               plid = sts->playlist;
+                               track = sts->song;
+                       }
+                       if (sts->elapsedTime != last_time) {
+                               send_timer(sts->elapsedTime);
+                               last_time = sts->elapsedTime;
+                       }
+               }
+               mpd_freeStatus(sts);
+
+               poll(&pfd, 1, 100);
+               while ((ev = read_car2pc_event())) {
+                       uint16_t code;
+
+                       code = CAR2PC_CMD(ev[0], ev[1]);
+                       if (code != CAR2PC_STOP && code != CAR2PC_PLAY)
+                               syslog(LOG_NOTICE, "Got Car2PC command '%s'\n",
+                                      ev);
+
+                       switch(code) {
+                       case CAR2PC_STOP:
+                               want_state = MPD_STATUS_STATE_PAUSE;
+                               break;
+                       case CAR2PC_PLAY:
+                               want_state = MPD_STATUS_STATE_PLAY;
+                               break;
+                       case CAR2PC_TRACK:
+                               change_track(atoi(ev+2), nr_tracks);
+                               track = -1;
+                               break;
+                       case CAR2PC_NEXT_TRACK:
+                               mpd_sendNextCommand(mpd);
+                               mpd_finishCommand(mpd);
+                               break;
+                       case CAR2PC_PREV_TRACK:
+                               if (track == nr_tracks)
+                                       change_track(nr_tracks, nr_tracks);
+                               else {
+                                       mpd_sendPrevCommand(mpd);
+                                       mpd_finishCommand(mpd);
+                               }
+                               break;
+                       case CAR2PC_NEXT_DISC:
+                               change_disc(1);
+                               break;
+                       case CAR2PC_PREV_DISC:
+                               change_disc(-1);
+                               break;
+                       default:
+                               syslog(LOG_ERR, "Unknown event from car2pc: '%s'\n",
+                                      ev);
+                       }
+                       free(ev);
+               }
+       }
+}
+
+void connect_car2pc(char *port)
+{
+       struct termios tio;
+
+       car2pc_fd = open(port, O_RDWR);
+       if (car2pc_fd < 0) {
+               syslog(LOG_ERR, "Failed to open port %s: %s\n",
+                      port, strerror(errno));
+               exit(1);
+       }
+       fcntl(car2pc_fd, F_SETFL, O_NONBLOCK | fcntl(car2pc_fd, F_GETFL));
+       tcgetattr(car2pc_fd, &tio);
+       cfsetospeed(&tio, B9600);
+       cfsetispeed(&tio, B9600);
+       tio.c_cflag &= ~PARENB;
+       tcsetattr(car2pc_fd, TCSANOW, &tio);
+}
+
+void connect_mpd(void)
+{
+       const char *host = getenv("MPD_HOST");
+       const char *port = getenv("MPD_PORT");
+       char *passwd = NULL;
+       char *tmp;
+       int portno = 6600;
+
+       if (port) {
+               char *end;
+               portno = strtol(port, &end, 10);
+               if (!portno || *end) {
+                       syslog(LOG_ERR, "Invalid $MPD_PORT setting\n");
+                       exit(1);
+               }
+       }
+       if (!host)
+               host = "localhost";
+
+       tmp = strchr(host, '@');
+       if (tmp) {
+               int pwlen = tmp - host;
+
+               passwd = strdup(host);
+               if (!passwd) {
+                       syslog(LOG_ERR, "Failed to allocate memory\n");
+                       exit(1);
+               }
+               passwd[pwlen] = 0;
+               host += pwlen + 1;
+       }
+       mpd = mpd_newConnection(host, portno, 60.0);
+       if (!mpd) {
+               syslog(LOG_ERR, "mpd_newConnection() returns NULL\n");
+               exit(1);
+       }
+       if (mpd->error) {
+               syslog(LOG_ERR, "mpd_NewConnection(): %s\n", mpd->errorStr);
+               exit(1);
+       }
+
+       if (!passwd)
+               return;
+
+       mpd_sendPasswordCommand(mpd, passwd);
+       if (mpd->error) {
+               syslog(LOG_ERR, "mpd_sendPasswordCommand(): %s\n",
+                      mpd->errorStr);
+               exit(1);
+       }
+       mpd_finishCommand(mpd);
+       if (mpd->error) {
+               syslog(LOG_ERR, "mpd_finishCommand(): %s\n",
+                      mpd->errorStr);
+               exit(1);
+       }
+
+       free(passwd);
+}
+
+int main(int argc, char **argv)
+{
+       char *car2pc_port;
+       struct sockaddr_in sin;
+       int port;
+       pid_t pid;
+
+       if (0) {
+               pid = fork();
+               if (pid)
+                       exit(0);
+       }
+
+       openlog("mpc-car2pc", LOG_PID|LOG_PERROR, LOG_DAEMON);
+
+       if (argc != 2) {
+               syslog(LOG_ERR, "Usage: mpc-car2pc <port>\n");
+               exit(1);
+       }
+
+       /* We want the car2pc's serial port to be our controlling TTY,
+          because we want to get SIGHUP when it goes away. */
+       if (setsid() == -1)
+               perror("setsid");
+       signal(SIGHUP, &handle_hup);
+
+       connect_car2pc(argv[1]);
+       connect_mpd();
+
+       syslog(LOG_NOTICE, "mpc-car2pc starting up on port %s\n", argv[1]);
+
+       mainloop();
+
+       syslog(LOG_NOTICE, "mpc-car2pc exiting\n");
+}