-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvdr.py
197 lines (171 loc) · 7.51 KB
/
vdr.py
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
189
190
191
192
193
194
195
196
197
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (C) 2011 Wolfgang Rohdewald <[email protected]>
halirc is free software you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
import os, subprocess
from twisted.internet.endpoints import TCP4ClientEndpoint
from twisted.internet.defer import Deferred, succeed
from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory
from lib import Serializer, SimpleTelnet, Message, LOGGER, logDebug, elapsedSince
class VdrMessage(Message):
"""holds content of a message from or to Vdr"""
def __init__(self, decoded=None, encoded=None):
"""for the VDR we only use the machine form, its
readability is acceptable"""
Message.__init__(self, decoded, encoded)
def command(self):
"""the human readable command"""
parts = self._encoded.split()
if parts[0].lower() == 'plug':
return ' '.join(parts[:3])
else:
return ' '.join(parts[:1])
def value(self):
"""the human readable value"""
parts = self._encoded.split()
if parts[0].lower() == 'plug':
return ' '.join(parts[3:])
else:
return ' '.join(parts[1:])
class VdrProtocol(SimpleTelnet):
"""talk to vdr"""
# pylint: disable=R0904
# pylint finds too many public methods
def __init__(self):
self.wrapper = None
SimpleTelnet.__init__(self)
def lineReceived(self, line):
"""we got a full line from vdr"""
logDebug(self, 'p', 'READ from {}: {}'.format(self.wrapper.name(), repr(line)))
if line.startswith('221 '):
# this is an error because we should have
# closed the connection ourselves after a
# much shorter timeout than the server timeout
LOGGER.error('vdr closes connection, timeout')
self.wrapper.close()
return
if line.startswith('220 '):
self.wrapper.openDeferred.callback(None)
return
if line.split(' ')[0] not in ['250', '354', '550', '900', '910', '911']:
LOGGER.error('from {}: {}'.format(self.wrapper.name(), line))
if self.wrapper.tasks.running:
self.wrapper.tasks.gotAnswer(VdrMessage(line))
else:
LOGGER.error('vdr sent data without being asked:{}'.format(line))
class Vdr(Serializer):
"""talks to VDR. This is a wrapper around the Telnet protocol
becaus we want to automatically close the connection after
some timeout and automatically reopen it when needed. Vdr
can only handle one client simultaneously."""
# TODO: an event generator watching syslog for things like
# switching channel
eol = '\r\n'
message = VdrMessage
def __init__(self, hal, host='localhost', port=6419):
Serializer.__init__(self, hal)
self.host = host
self.port = port
self.protocol = None
self.openDeferred = None
self.prevChannel = None
self.closeTimeout = 5
self.kodiProcess = None
def open(self):
"""open connection if not open"""
def gotProtocol(result):
"""now we have a a connection, save it"""
self.protocol = result
self.protocol.wrapper = self
if not self.protocol:
logDebug(self, None, 'opening vdr')
point = TCP4ClientEndpoint(reactor, self.host, self.port)
factory = ClientFactory()
factory.protocol = VdrProtocol
point.connect(factory).addCallback(gotProtocol)
self.openDeferred = Deferred()
result = self.openDeferred
else:
result = succeed(None)
reactor.callLater(self.closeTimeout, self.close)
return result
def close(self):
"""close connection if open"""
if self.protocol:
if not (self.tasks.running or self.tasks.queued):
if elapsedSince(self.tasks.allRequests[-1].sendTime) > self.closeTimeout - 1:
logDebug(self, None, 'closing vdr after closeTimeout {}'.format(self.closeTimeout))
self.write('quit\n')
self.protocol.transport.loseConnection()
self.protocol = None
def write(self, data):
self.protocol.transport.write(data)
def send(self, *args):
"""unconditionally send cmd"""
_, msg = self.args2message(*args)
return self.push(msg)
def getChannel(self, dummyResult=None):
"""returns current channel number and name"""
def got(result):
"""we got the current channel"""
result = result.decoded.split(' ')
if result[0] != '250':
return None, None
else:
return result[1], ' '.join(result[2:])
return self.push(self.message('chan')).addCallback(got)
def gotoChannel(self, dummyResult, channel):
"""go to a channel if not yet there.
Channel number and name are both accepted."""
def got(result):
"""we got the current channel"""
if channel not in result:
self.prevChannel = result[0]
return self.push(self.message('chan %s' % channel))
else:
return succeed(None)
return self.getChannel().addCallback(got)
def toggleSofthddevice(self, dummyResult):
"""toggle softhddevice output between on and off"""
def startKodi(dummyResult):
"""start kodi"""
if self.kodiProcess:
return
environ = dict(os.environ)
environ['DISPLAY'] = ':0'
environ['HOME'] = '/home/wr'
self.kodiProcess = subprocess.Popen(["kodi", "-fs"], env=environ)
logDebug(self, None, 'started kodi process {}'.format(self.kodiProcess.pid))
def _remoteOff(dummyResult):
"""disable remote control"""
return self.send('remo off')
def _toggle1(result):
"""result ends in NOT_SUSPENDED or SUSPEND_NORMAL"""
if result.value().endswith(' NOT_SUSPENDED'):
return self.send('plug softhddevice susp').addCallback(_remoteOff).addCallback(startKodi)
elif result.value().endswith(' SUSPEND_NORMAL'):
if self.kodiProcess:
logDebug(self, None, 'killing kodi process {}'.format(self.kodiProcess.pid))
self.kodiProcess.kill() # would be nice to terminate cleanly
_ = self.kodiProcess.wait()
self.kodiProcess = None
subprocess.Popen(['killall', '-9', 'kodi.bin']).wait()
reactor.callLater(7, self.send, 'plug softhddevice resu')
return self.send('remo on')
else:
LOGGER.error('plug softhddevice stat returns unexpected answer:{}'.format(repr(result)))
return succeed(None)
return self.ask('plug softhddevice stat').addCallback(_toggle1)