summaryrefslogtreecommitdiffstats
path: root/daemon.py
blob: f32bb0594c98135784d716e45d054ce3aaf95bf7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/python

import serial
import mpd
import time
import sys
import logging

logger = logging.getLogger()
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger.setLevel(logging.WARNING)

class Config:
    display_lines = 2
    display_width = 16
    display_length = display_lines * display_width

class DaemonException(Exception):
    pass

class Daemon:
    line = ""
    playing = False

    def __init__(self):
        try:
            self.connect()
        except (serial.SerialException):
            raise DaemonException()

    def connect(self):
        self.ser = serial.Serial(sys.argv[1], 9600, timeout=1)
        logger.debug("connected. waiting for client greeting")

        # raise timeout to let arduino start up
        self.ser.timeout = 15
        incoming = self.readline()

        # arduino sometimes has a dirty buffer
        # and sends junk before the ready message
        if not incoming.endswith("ready"):
            logger.error("Got reply: '"+incoming+"'")
            raise Exception("Wrong or no client greeting")
        self.ser.timeout = 1
        logger.debug("got client greeting")

        self.client = mpd.MPDClient()
        self.client.connect("localhost", 6600)
        logger.debug("connected to mpd")

    def readline(self):
        logger.debug("Reading from serial")

        try:
            return self.ser.readline().decode("ascii").strip()
        except (serial.SerialException):
            logger.warning("Got exception when reading, trying to close connection")
            self.ser.close()
            raise DaemonException()

    def write(self, msg):
        logger.debug("Writing '%s' to serial", msg)

        try:
            return self.ser.write(msg)
        except (serial.SerialException, OSError):
            logger.warning("Got exception when writing, trying to close connection")
            self.ser.close()
            raise DaemonException()

    def inWaiting(self):
        logger.debug("Checking inWaiting for serial")

        try:
            return self.ser.inWaiting()
        except (serial.SerialException, OSError):
            logger.warning("Got exception when inWaiting, trying to close connection")
            self.ser.close()
            raise DaemonException()

    def update(self):
        old_line = self.line
        old_playing = self.playing
        changed = False

        incoming = self.client.currentsong()

        try:
            self.line = incoming["artist"] + " - " + incoming["title"]
        except KeyError:
            self.line = " -- NO INFO --"

        if self.line != old_line:
            changed = True

        self.playing = self.client.status()["state"] == "play"
        if self.playing != old_playing:
            changed = True

        return changed

    def run(self):
        self.update()

        last_display_update = 0.0
        last_song_update = 0.0
        now = 0.0
        time_diff = 0.0
        current_position = 0

        while 1:
            now = time.time()

            # check for incoming commands from arduino
            if self.inWaiting() > 0:
                incoming = self.readline()

                logger.info("got serial command: '%s'", incoming)

                functions = {
                    "next": self.client.next,
                    "previous": self.client.previous,
                    "pause": self.client.pause,
                }
                try:
                    func = functions[incoming]
                    func()

                    # force redraw if anything changed
                    if self.update():
                        last_display_update = 0
                        current_position = 0

                except KeyError:
                    logger.warning("ignoring unknown serial command '%s'", incoming)
                    pass

            # check if song changed
            # TODO: use idle and poll (more often)?
            time_diff = now - last_song_update
            if time_diff > 2.5 or time_diff < -60.0:
                last_song_update = now
                if self.update():
                    last_display_update = 0
                    current_position = 0

            # update arduino display
            time_diff = now - last_display_update
            if time_diff > 0.8 or time_diff < -60.0:
                last_display_update = now
                if self.playing:
                    i = current_position

                    if i - 1 + Config.display_length == len(self.line):
                        i = 0

                    if i == 0:
                        # allow people to start reading
                        last_display_update += 2

                    if len(self.line) > Config.display_length:
                        buf = self.line[i:i+Config.display_length]
                    else:
                        buf = self.line.ljust(Config.display_length, " ")

                    i += 1
                    current_position = i
                else:
                    buf = "-- PAUSED --".center(Config.display_width, " ").ljust(Config.display_length, " ")

                    # always start at the beginning if we unpause
                    current_position = 0

                logger.debug("current_position = %s, buf = '%s'", str(current_position).rjust(3), buf)
                self.write(bytes(buf, "ascii", "replace"))

            time.sleep(0.05)

if __name__ == '__main__':
    while 1:
        try:
            d = Daemon()
            d.run()
        except DaemonException as e:
            obj = sys.exc_info()[0].__name__
            logger.warning("ignored {0}: {1}".format(obj, e))
            time.sleep(1)
            pass