--- /dev/null
+#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");
+}