From: David Woodhouse Date: Wed, 20 May 2009 21:39:02 +0000 (+0100) Subject: initial commit X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=f761441e45b935affa7b398151365b823853ecf0;p=users%2Fdwmw2%2Fmpc-car2pc.git initial commit --- f761441e45b935affa7b398151365b823853ecf0 diff --git a/62-car2pc.rules b/62-car2pc.rules new file mode 100644 index 0000000..4d59a31 --- /dev/null +++ b/62-car2pc.rules @@ -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 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 index 0000000..3ae68c0 --- /dev/null +++ b/mpc-car2pc.c @@ -0,0 +1,397 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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"); +}