|
- #!/usr/bin/env python
-
- from twisted.internet.protocol import BaseProtocol, Protocol, ConnectedDatagramProtocol
- from twisted.internet.serialport import SerialPort
- from twisted.internet import defer, reactor, threads
- from zope.interface import implements, Interface, Attribute
-
- import array
- import cddb
- import serial
- import sys
- import time
-
- class ICDPlayerTransport(Interface):
- power = Attribute('A C{bool}')
- playing = Attribute('A C{bool}')
- paused = Attribute('A C{bool}')
- dooropen = Attribute('A C{bool}')
- discknown = Attribute('A C{bool}')
- curdisc = Attribute('A C{int} of the current disc loaded or None.')
- alldiscs = Attribute('A C{bool}, does playback include all discs.')
- repeateall = Attribute('A C{bool}')
- repeateone = Attribute('A C{bool}')
- shuffle = Attribute('A C{bool}')
- program = Attribute('False or 1, 2 or 3')
- cdnum = Attribute('A number that is the CD player ID')
- disccount = Attribute('A count of the capacity of the player.')
- model = Attribute('The model.')
-
- def play():
- '''Start playing at current possition.'''
-
- def stop():
- '''Stop playing.'''
-
- def pause():
- '''Pause.'''
-
- def togglepause():
- '''Toggle Paused state. Will trigger the respective paused
- or playing on the protocol.'''
-
- def nexttrack():
- '''Next Track.'''
-
- def previoustrack():
- '''Previous Track.'''
-
- def poweron():
- '''Turn power on. Returns a defered that will be triggered
- when power is on.'''
-
- def poweroff():
- '''Turn power off.'''
-
- def discinfo(d):
- '''Report disc info, will stop playback. Returns Deferred.'''
-
- def trackinfo(d, t):
- '''Report track info, will stop playback. Returns Deferred.'''
-
- def playdisc(disc, track=1):
- '''Start playing disc. Returns Deferred.'''
-
- def cuedisc(disc, track=1):
- '''Cue disc to track. Returns Deferred.'''
-
- def loaddisc(disc, track=1, load=True):
- '''Make sure disc is loaded, cue if necessary. Returns a
- Deferred. If load is False, will not load the disc. The
- Deferred will return True if the disc is loaded, otherwise
- False.'''
-
- def discs():
- '''Return set of discs present, or None if the discs are
- unknown at this time.'''
-
- def __contains__(k):
- '''Check if a disc is present in the changer.'''
-
- def __len__(k):
- '''Number of discs present in the changer. This is not the
- capacitity..'''
-
- def __iter__():
- '''Iterate over all the discs in the changer.'''
-
- def setcontinuous(alldiscs=True):
- '''Set continuous playback, if alldiscs is False, one disc.'''
-
- def setshuffle(alldiscs=True):
- '''Set shuffle playback, if alldiscs is False, one disc.'''
-
- def settimeout(timeout=300):
- '''After timeout seconds of no commands being issues, and
- the player not playing discs, power off.'''
-
- def getcddbid(disc):
- '''Return CDDB id of disc.'''
-
- class CDPlayerProtocol(BaseProtocol):
- '''This protocol is a bit different in that the transport implements
- the CDPlayerTransport interface.'''
-
- def stateChanged(self):
- # one of the attributes changes
- pass
-
- class SLinkException(Exception):
- pass
-
- class DiscError(SLinkException):
- def __init__(self, disc, s):
- SLinkException.__init__(self, s % disc)
- self.disc = disc
-
- class NoDisc(SLinkException):
- pass
-
- # Not really BaseProtocol
- class SLinkCDTransport(object):
- implements(ICDPlayerTransport)
-
- def __init__(self, sprotocol, cdnum):
- if cdnum not in (1, 2, 3):
- raise ValueError('invalid CD: %d' % cdnum)
- self._proto = sprotocol
- self._cdnum = cdnum
- self._childprotocol = None
-
- # Internal state variables
- self._discs = None
- self._discpos = None
- self._disccount = None
-
- self.loadeddisc = None
- self._responses = {}
-
- def makeConnection(self, protocol):
- print 'mC'
- self._childprotocol = protocol
- # We can now start issuing commands to _proto, our parent.
- # Once we have gathered all the state we need, we will
- # connect to our child protocol.
- l = []
- d = self.runCommand('queryplayercap', 'decksize')
- d.addCallback(lambda x: self.printargs('c'))
- l.append(d)
-
- d = self.runCommand('querydeckmodel', 'deckmodel')
- d.addCallback(self.processdeckmodel)
- d.addCallback(lambda x: self.printargs('b'))
- l.append(d)
-
- d = self.runCommand('querystatus', 'status')
- d.addCallback(lambda x: self.printargs('after querystatus done'))
- l.append(d)
-
- d = defer.DeferredList(l)
- d.addCallback(lambda x: self.printargs('here'))
- d.addCallback(lambda x, p=protocol, s=self: p.makeConnection(s))
-
- def printargs(self, args):
- print 'mpa:', `args`
-
- def processdeckmodel(self, args):
- self._model = args[0]
-
- def processstatus(self, args):
- a = array.array('B', args[0])
- f = a[0]
- r = None
- if f & 0x10:
- self._power = False
- else:
- self._power = True
-
- if f & 0x2f == 0x2f:
- self._dooropen = True
- self._playing = False
- self._paused = False
- else:
- self._dooropen = False
- if f & 0x1:
- self._playing = True
- else:
- self._playing = False
- if f & 0x2:
- self._paused = True
- else:
- self._paused = False
-
- f = a[1]
- if f & 0x80:
- self._discknown = True
- if self._discs is None:
- r = self.getdiscs()
- else:
- self._discknown = False
- #self._discs = None
-
- if self._power is True:
- # We don't know discs yet, try again in a few
- # seconds.
- r = defer.Deferred()
- reactor.callLater(1, lambda:
- self.runCommand('querystatus',
- 'status').chainDeferred(r))
-
- if f & 0x40:
- self._alldiscs = True
- else:
- self._alldiscs = False
-
- if f & 0x10:
- self._repeateall = True
- self._repeateone = False
- elif f & 0x20:
- self._repeateall = False
- self._repeateone = True
- else:
- self._repeateall = False
- self._repeateone = False
-
- if f & 0xf == 0x1:
- self._shuffle = True
- self._program = False
- elif f & 0xf in (0x4, 0x5, 0x6):
- self._shuffle = False
- self._program = (f & 0xf) - 0x3
- else:
- self._shuffle = False
- self._program = False
-
- print 'self:', `self`, `r`
- return r
-
- def __repr__(self):
- return '<SLinkCDTransport: %s>' % ', '.join('%s=%s' % (k, `getattr(self, k)`) for k in ( 'power', 'playing', 'paused', 'dooropen', 'discknown', 'alldiscs', 'repeateall', 'repeateone', 'shuffle', 'program', 'disccount', 'model', ))
-
- def getdiscs(self):
- print 'gd called'
- self._discsnew = set()
- l = self.runCommand('querydiscs', [ 'disc_list%d' % i for i in xrange((self.disccount + 103) / 104) ])
- for i, d in enumerate(l):
- d.addCallback(lambda x, i=i: self.processdisclist(i, x))
-
- r = defer.DeferredList(l)
- r.addCallback(self.freezediscs)
- return r
-
- def freezediscs(self, args):
- print 'fd called'
- self._discs = frozenset(self._discsnew)
- self._discsnew = None
-
- bits = dict((1 << i, i) for i in xrange(8))
-
- def processdisclist(self, part, args):
- off = part
- a = array.array('B', args[0])
- for pos, i in enumerate(a):
- while i:
- b = i & -i
- bitpos = self.bits[b]
- i ^= b
- self._discsnew.add(104 * off + pos * 8 +
- bitpos + 1)
-
- def insertresponse(self, resp, obj):
- if resp in self._responses:
- self._responses[resp].append(obj)
- else:
- self._responses[resp] = [ obj ]
-
- def runCommand(self, cmd, response, *args, **kwargs):
- for i in kwargs.pop('ignoreresponses', []):
- d = defer.Deferred()
- self.insertresponse(i, d)
-
- if isinstance(response, list):
- d = []
- for i in response:
- dd = defer.Deferred()
- self.insertresponse(i, dd)
- d.append(dd)
- else:
- d = defer.Deferred()
- self.insertresponse(response, d)
-
- self._proto.sendmsg(cmd, self._cdnum, *args)
- return d
-
- def gotResponse(self, cmd, *args):
- # Got a response from a command
- expected = True
-
- print 'gR:', `cmd`, `args`
- # A few commands are state change, so handle them here.
- r = None
- if cmd in ('nowat', 'tocread', 'trackinfo', 'track_info', 'discinfo', ):
- self._discpos = args[0]
- elif cmd == 'poweron':
- print 'poweron, doing querystatus'
- r = self.runCommand('querystatus', 'status')
- elif cmd == 'poweroff':
- self._discpos = None
- r = self.runCommand('querystatus', 'status')
- elif cmd == 'decksize':
- if self._disccount is not None and self._disccount != \
- args[0]:
- # XXX state changed!
- pass
- self._disccount = args[0]
- elif cmd == 'status':
- r = self.processstatus(args)
- #elif cmd == 0x18
- # self._discs = None
- else:
- expected = False
-
- if r is not None:
- r.addCallback(lambda x: self.finishResponse(cmd,
- expected, *args))
- return
-
- self.finishResponse(cmd, expected, *args)
-
- def finishResponse(self, cmd, expected, *args):
- if cmd not in self._responses:
- if not expected:
- print 'Unexpected response:', `cmd`, `args`
- return
-
- # We pop so that a future command will be scheduled properly
- for i in self._responses.pop(cmd):
- i.callback(args)
-
- power = property(lambda x: x._power)
- playing = property(lambda x: x._playing)
- paused = property(lambda x: x._paused)
- dooropen = property(lambda x: x._dooropen)
- discknown = property(lambda x: x._discknown)
- curdisc = property(lambda x: x._discpos)
- alldiscs = property(lambda x: x._alldiscs)
- repeateall = property(lambda x: x._repeateall)
- repeateone = property(lambda x: x._repeateone)
- shuffle = property(lambda x: x._shuffle)
- program = property(lambda x: x._program)
- cdnum = property(lambda x: x._cdnum)
- disccount = property(lambda x: x._disccount)
- model = property(lambda x: x._model)
-
- def play(self):
- '''Start playing at current possition.'''
- return self.runCommand('play', 'play')
-
- def stop(self):
- '''Stop playing.'''
- return self.runCommand('stop', 'stop')
-
- def pause(self):
- '''Pause.'''
- return self.runCommand('pause', 'pause')
-
- def togglepause(self):
- '''Toggle Paused state. Will trigger the respective paused
- or playing on the protocol.'''
- l = self.runCommand('togglepause', [ 'play', 'pause', ])
- return defer.DeferList(l, fireOnOneCallback=True,
- fireOnOneErrback=True)
-
- def nexttrack(self):
- '''Next Track.'''
- raise NotImplementedError
-
- def previoustrack(self):
- '''Previous Track.'''
- raise NotImplementedError
-
- def poweron(self):
- '''Turn power on.'''
- if not self.power:
- return self.runCommand('poweron', 'poweron')
-
- return defer.succeed(())
-
- def poweroff(self):
- '''Turn power off.'''
- if self.power:
- return self.runCommand('poweroff', 'poweroff',
- ignoreresponses=[ 'stop', ])
-
- return defer.succeed(())
-
- def discinfo(self, disc):
- '''Report disc info, will stop playback. Returns Deferred.'''
- print 'di:, scheduling loaddisc'
- return self.loaddisc(disc) \
- .addCallback(lambda x:
- self.runCommand('querydiscinfo', 'discinfo', disc))
-
- def trackinfo(self, disc, track):
- '''Report track info, will stop playback. Returns Deferred.'''
- print 'ti:, scheduling loaddisc'
- return self.loaddisc(disc).addCallback(lambda x:
- self.runCommand('querytrackinfo', 'track_length', disc,
- track))
-
- def playdisc(self, disc, track=1):
- '''Start playing disc. Returns Deferred.'''
- return self.loaddisc(disc, track).addCallback(lambda x:
- self.runCommand('playtrack', 'track_info', disc, track))
-
- def cuedisc(self, disc, track=1, noload=False):
- '''Cue disc to track. Returns Deferred.'''
- # XXX - should we wait for the pause response?
- # XXX - we'll recurse, but that should be fine
- if noload:
- print 'z'
- d = defer.succeed(None)
- else:
- print 'w'
- d = self.loaddisc(disc, track)
- # XXX - second cue doesn't return track_info or something
- d.addCallback(lambda x: self.stop())
- return d.addCallback(lambda x:
- self.runCommand('cuetrack', 'track_info', disc, track,
- ignoreresponses=[ 'pause', ]))
-
- def loaddisc(self, disc, track=1, load=True):
- '''Make sure disc is loaded, cue if necessary. Returns Deferred.'''
- if self._discpos != disc and load:
- print 'x', `self._discpos`, `disc`
- d = self.poweron()
- d.addCallback(lambda x: self.setcontinuous())
- d.addCallback(lambda x: self.cuedisc(disc, noload=True))
- else:
- print 'y'
- d = defer.succeed(None)
-
- d.addCallback(lambda x: self._discpos == disc)
- return d
-
- def discs(self):
- '''Return set of discs present.'''
- return self._discs
-
- def __contains__(self, k):
- '''Check if a disc is present in the changer.'''
- return k in self._discs
-
- def __len__(self):
- '''Number of discs present in the changer. This is not the
- capacitity..'''
- return len(self._discs)
-
- def __iter__(self):
- '''Iterate over all the discs in the changer.'''
-
- # We use this so that when the discs available changes we
- # automaticly update this.
- for i in xrange(self.disccount):
- if i in self:
- yield i
-
- def setmode(self, fun):
- print 'sm:', `fun`
- if fun():
- print 'passed!', `self`
- return
-
- d = defer.Deferred()
- self.insertresponse('status', d)
- d.addCallback(lambda x: self.setmode(fun))
- # send IR code
- print 'sending ir'
- self._proto.sendir('\xeb\x91')
- return d
-
- def setcontinuous(self, alldiscs=True):
- '''Set continuous playback, if alldiscs is False, one disc.'''
- fun = lambda: not self.program and not self.shuffle and \
- self.alldiscs == alldiscs
- if not fun() and (self.playing or self.paused):
- d = self.stop()
- else:
- d = defer.succeed(None)
-
- return d.addCallback(lambda x: self.setmode(fun))
-
- def setshuffle(self, alldiscs=True):
- '''Set shuffle playback, if alldiscs is False, one disc.'''
- if self.playing or self.paused:
- d = self.stop()
- raise NotImplementedError
-
- def settimeout(self, timeout=300):
- '''After timeout seconds of no commands being issues, and
- the player not playing discs, power off.'''
- raise NotImplementedError
-
- # Imported from eslink.py, w/ minor changes
- @defer.deferredGenerator
- def getcddbid(self, disc):
- '''Return CDDB id of disc.'''
-
- r = defer.waitForDeferred(self.discinfo(disc))
- yield r
- r = r.getResult()
- if r[0] != disc:
- # if the disc isn't present, update the discs
- print 'Disc missing, updating discs'
- r = defer.waitForDeferred(self.getdiscs())
- yield r
- r = r.getResult()
- raise NoDisc(disc, 'disc %d not present')
-
- r = r[1:]
- def addreducems(i, a):
- ni = (i[0] + a[0], i[1] + a[1])
- m, s = divmod(ni[1], 60)
- i = list(i)
- i[0] = ni[0] + m
- i[1] = s
- return tuple(i)
-
- tracks = []
- curtot = (0, 2)
- tracklens = []
- for i in range(1, r[0] + 1):
- tracks.append(curtot)
- j = defer.waitForDeferred(self.trackinfo(disc, i))
- yield j
- j = j.getResult()
- assert j[0] == disc
- assert j[1] == i
- j = j[2:]
- tracklens.append(j)
- curtot = addreducems(curtot, tracklens[-1])
-
- tracks.append(r[1:])
- tracks[-1] = addreducems(tracks[-1], (0, 2))
- id = cddb.discid(tracks)
-
- yield id
-
- def nulltrim(buf):
- s = buf.find('\x00')
- if s == -1:
- return buf
-
- return buf[:s]
-
- class SLinkProtocol(ConnectedDatagramProtocol):
- '''Interface for an S-Link Port. To be passed into something that
- passes messages to/from an S-Link Port.'''
-
- simpcmds = {
- 'play': '\x00',
- 'stop': '\x01',
- 'pause': '\x02',
- 'togglepause': '\x03',
- 'nexttrack': '\x08',
- 'queryplayercap': '\x22',
- 'poweron': '\x2e',
- 'poweroff': '\x2f',
- }
- _bysimpcmds = dict((v, k) for k, v in simpcmds.iteritems())
- #_bysimpcmds['\x0e'] = 'poweron' # Disc loaded?
-
- simpcmds['querystatus'] = '\x0f'
- simpcmds['querydiscinfo'] = '\x44'
- simpcmds['querytrackinfo'] = '\x45'
- simpcmds['playtrack'] = '\x50'
- simpcmds['cuetrack'] = '\x51'
- simpcmds['querydeckmodel'] = '\x6a'
- simpcmds['querydiscs'] = '\x72'
-
- # Bytes returned by cmd 0x61
- decksizes = {
- '\x05\x63': 5,
- '\xfe\x0b': 200,
- '\x64\x6b': 300,
- '\x64\x0b': 300,
- '\xfe\x6b': 300,
- '\xc8\x6b': 400,
- }
-
- def discdecode(hidisc, x):
- disc = array.array('B', x)[0]
- if hidisc:
- return disc + 200
- elif disc == 0x00:
- return 100 # XXX?
- elif disc >= 0x9a:
- return 100 + disc - 0x9a
- else:
- return bcdtoint(disc)
-
- def discinfo(hidisc, x, dd=discdecode):
- a = array.array('B', x)
- return dd(hidisc, x), bcdtoint(a[2]), \
- bcdtoint(a[3]), bcdtoint(a[4]), bcdtoint(a[5])
-
- def trackinfo(hidisc, x, dd=discdecode):
- a = array.array('B', x)
- return dd(hidisc, x), bcdtoint(a[1]), \
- bcdtoint(a[2]), bcdtoint(a[3])
-
- responses = {
- '\x0e': lambda hidisc, x: ('invalidmode', ()),
- '\x14': lambda hidisc, x: ('discinfo', (None, )),
- '\x15': lambda hidisc, x: ('trackinfo', (None, )),
- '\x45': lambda hidisc, x: ('trackinfo', (x, )),
- '\x50': lambda hidisc, x, ti=trackinfo: ('track_info',
- ti(hidisc, x)),
- '\x52': lambda hidisc, x, dd=discdecode: ('tocread',
- (dd(hidisc, x),)),
- '\x58': lambda hidisc, x, dd=discdecode: ('nowat',
- (dd(hidisc, x),)),
- '\x60': lambda hidisc, x, di=discinfo: ('discinfo',
- di(hidisc, x)),
- '\x61': lambda hidisc, x, y=decksizes: ('decksize',
- (y[x],)),
- '\x62': lambda hidisc, x, ti=trackinfo: ('track_length',
- ti(hidisc, x)),
- '\x6a': lambda hidisc, x: ('deckmodel',
- (nulltrim(x).decode('ascii'),)),
- '\x70': lambda hidisc, x: ('status', (x, )),
- '\x72': lambda hidisc, x: ('disc_list0', (x, )),
- '\x73': lambda hidisc, x: ('disc_list1', (x, )),
- '\x74': lambda hidisc, x: ('disc_list2', (x, )),
- '\x75': lambda hidisc, x: ('disc_list3', (x, )),
- }
-
- def __init__(self):
- self._queue = []
- self._cdplayers = {}
- self._pendingmsgs = {}
- self._started = False
- # Should be set by class
-
- def startProtocol(self):
- self._started = True
- for fun, arg in self._queue:
- fun(arg)
- self._queue = None
-
- def stopProtocol(self):
- self._started = False
-
- def connectCDPlayer(self, num, protocol):
- if num < 1 or num > 3:
- raise ValueError('invalid CD number: %d' % num)
-
- if not isinstance(protocol, CDPlayerProtocol):
- raise ValueError('protocol must be CDPlayerProtocol')
-
- if num in self._cdplayers:
- raise ValueError('CD player already attached')
-
- transport = SLinkCDTransport(self, num)
- if not self._started:
- self._queue.append((transport.makeConnection,
- protocol))
- else:
- transport.makeConnection(protocol)
-
- self._cdplayers[num] = transport
-
- def datagramReceived(self, msg, port):
- '''Decode message and dispatch (if possible).'''
-
- mar = array.array('B', msg)
-
- if (mar[0] & 0xf0) != 0x90:
- print 'unknown msg:', msg.encode('hex')
-
- if msg in self._pendingmsgs:
- self._pendingmsgs[msg].cancel()
- del self._pendingmsgs[msg]
- return
-
- if (mar[0] & 0x8) != 0x8:
- # command from another device, ignore
- #print 'unkn command:', msg.encode('hex')
- return
-
- # From here all msgs are responses
- cdnum = (mar[0] & 0x3) + 1
- hidisc = False
- if cdnum > 3:
- hidisc = True
- cdnum -= 3
-
- if cdnum not in self._cdplayers:
- # We don't have this CD Player registered.
- print 'unkn responses %d:' % cdnum, msg.encode('hex')
- return
-
- if len(msg) == 2 and msg[1] in self._bysimpcmds:
- attr = self._bysimpcmds[msg[1]]
- args = ()
- elif msg[1] in self.responses:
- attr, args = self.responses[msg[1]](hidisc, msg[2:])
- else:
- print 'foo:', msg.encode('hex')
- return
- try:
- self._cdplayers[cdnum].gotResponse(attr, *args)
- except TypeError, e:
- if str(e) == 'gotResponse() argument after * must be a sequence':
- raise TypeError('args for %s not sequence' % `msg[1].encode('hex')`)
- else:
- print 'te:', `str(e)`
- raise
-
- # Imported from eslink.py
- def makeaddrcmd(self, msgto, cmd, cdnum, disc=None, track=None,
- hibit=False):
- addr = 0x90 + (cdnum - 1)
- if not msgto:
- addr |= 0x8
-
- if disc != None:
- if disc > 200:
- addr += 3
- disc -= 200
- elif disc < 100:
- disc = inttobcd(disc)
- else:
- disc = 0x9a + disc - 100
- if track is None:
- return '%c%c%c' % (addr, cmd, disc)
- else:
- track = inttobcd(track)
- return '%c%c%c%c' % (addr, cmd, disc, track)
- else:
- if hibit:
- addr += 3
- return '%c%c' % (addr, cmd)
-
- def schedulemsg(self, msg):
- print 'sending msg:', `msg`
- self.transport.write(msg)
- # XXX - should probably recall sendmsg to reschedule
- if msg in self._pendingmsgs:
- print 'old:', self._pendingmsgs[msg]
- self._pendingmsgs[msg] = reactor.callLater(1,
- lambda m=msg: self.schedulemsg(msg))
-
- def sendir(self, code):
- # XXX make it properly addressable
- self.transport.write(code)
-
- def sendmsg(self, type, cdpnum, disc=None, track=None):
- msg = self.makeaddrcmd(True, self.simpcmds[type], cdpnum,
- disc, track)
- self.schedulemsg(msg)
-
- def bcdtoints(i):
- t, o = divmod (i, 16)
- assert t < 10 and o < 10
-
- return t, o
-
- def inttobcd(i):
- assert i < 100, 'i is invalid: %s' % `i`
- t, o = divmod(i, 10)
-
- return t << 4 | o
-
- def bcdtoint(i):
- t, o = divmod (i, 16)
- assert t < 10 and o < 10
-
- return t * 10 + o
-
- class SLinkEPort(object):
- '''Implement one port transport for SLink-E.'''
-
- def __init__(self, slinke, port, protocol):
- self.slinke = slinke
- self.port = port
- self.protocol = protocol
- self.openned = True
-
- def write(self, msg):
- if not self.openned:
- raise RuntimeError('port has been closed')
-
- self.slinke.sendmsg(self.port, [ msg ])
-
- def writeSequence(self, data):
- self.slinke.sendmsg(self.port, data)
-
- def loseConnection(self):
- '''Close the port. Will return a Deferred object that is
- called when the port has finished closing.'''
-
- self.openned = False
- self.slinke.closePort(self.port)
-
- def getPeer(self):
- return self.port
-
- class SLinkE(Protocol):
- '''Interface for talking to the SLink-E. It sends commands, and
- dispatched commands recevied for the port.'''
-
- commands = {
- 'reset': '\xff\xff',
- 'getversion': '\xff\x0b',
- 'getserial': '\xff\x0c',
- 'disableport': lambda x: chr((x << 5) | 0x1f) + '\x02',
- 'enableport': lambda x: chr((x << 5) | 0x1f) + '\x03',
- }
-
- # XXX - It's assumed all responses are two bytes.
- receivedresp = {
- 'versionis': ('\xff\x0b', 1),
- 'portenabled': (lambda x: (ord(x[0]) & 0x1f) == 0x1f and
- x[1] == '\x03', 0),
- 'portdisabled': (lambda x: (ord(x[0]) & 0x1f) == 0x1f and
- x[1] == '\x02', 0),
- 'slinkerror': (lambda x: (ord(x[0]) & 0x1f) == 0x1f and
- x[1] == '\x80', 0),
- }
-
- version = property(lambda x: x._version)
- #serial = property(lambda x: x._serial)
-
- def __init__(self, *args, **kwargs):
- self._ports = {}
- self._portqueue = {}
- self._portpending = {}
- self._buffer = []
- self._bufferlen = 0
- self._currentresp = None
- self._bytesneeded = None
- _version = (None, None)
-
- def sendcmd(self, cmd):
- #print 'sending:', `cmd`
- self.transport.write(cmd)
-
- def openPort(self, port, protocol):
- if port in self._ports:
- raise ValueError('port already opened')
- if port in self._portpending:
- raise ValueError('port already pending')
-
- self.sendcmd(self.commands['enableport'](port))
- self._portpending[port] = SLinkEPort(self, port, protocol)
-
- def closePort(self, port):
- self.sendcmd(self.commands['disableport'](port))
-
- def sendmsg(self, port, msg):
- '''Send the sequence of strings in msg out the port.'''
- pos = 0
- fragpos = 0
- msgbuf = []
- while pos != len(msg) and fragpos != len(msg[-1]):
- fraglen = 0
- msgfrag = []
- while fraglen < 30 and pos != len(msg) and \
- fragpos != len(msg[-1]):
- msgfrag.append(msg[pos][fragpos:fragpos + (30 - fraglen)])
- fragpos += len(msgfrag[-1])
- fraglen += len(msgfrag[-1])
- if fragpos == len(msg[pos]):
- pos += 1
- fragpos = 0
-
- #print 'h:', `fraglen`, `msgfrag`
- msgbuf.append(chr((port << 5) | fraglen))
- msgbuf.extend(msgfrag)
-
- # Port Send End
- msgbuf.append(chr(port << 5))
-
- #print 'sending:', `msgbuf`
- self.transport.writeSequence(msgbuf)
-
- # Protocol Methods
- def connectionMade(self):
- Protocol.connectionMade(self)
- #self.sendcmd(self.commands['reset'])
- #time.sleep(1)
- self.sendcmd(self.commands['getversion'])
- #self.sendcmd(self.commands['disableport'](7))
- #for i in xrange(6):
- # self.sendcmd(self.commands['enableport'](i))
-
- def dataReceived(self, data):
- #print 'dR:', `data`
- self._buffer.append(data)
- self._bufferlen += len(data)
- i = True
- while i and self._buffer:
- i = self.checkBuffer()
-
- # Internal Buffer routines
- def peakBuffer(self, cnt):
- if cnt > self._bufferlen:
- return
-
- r = []
- rem = cnt
- for buf in self._buffer:
- r.append(buf[:rem])
- rem -= len(r[-1])
- if rem == 0:
- break
-
- r = ''.join(r)
- assert len(r) == cnt, 'failed: cnt=%d, buffer=%s' % (cnt, self._buffer)
- return r
-
- def consumeBuffer(self, cnt):
- assert self._bufferlen >= cnt
-
- rem = cnt
- while rem:
- buf = self._buffer.pop(0)
- self._bufferlen -= len(buf)
- if len(buf) > rem:
- self._buffer.insert(0, buf[rem:])
- self._bufferlen += len(buf) - rem
- return
-
- rem -= len(buf)
-
- def getBuffer(self, cnt):
- buf = self.peakBuffer(cnt)
- if buf is None:
- return
-
- self.consumeBuffer(cnt)
- return buf
-
- def lenBuffer(self):
- return self._bufferlen
-
- def checkBuffer(self):
- '''Dispatch messages if available in buffer. Returns True if
- a message was dispatched. Returns False if we need more data
- to dispatch.'''
-
- if self._currentresp is not None:
- raise NotImplementedError
-
- firstbyte = ord(self._buffer[0][0])
- portnum = firstbyte >> 5
- bytecnt = (firstbyte & 0x1f)
- #print 'portnum:', portnum, 'bytecnt:', bytecnt, 'buf:', self._buffer
- if bytecnt == 0:
- # port receive end
- self.consumeBuffer(1)
- if portnum not in self._portqueue:
- # XXX - stale data
- return
- msg = ''.join(self._portqueue[portnum])
- self._portqueue[portnum] = []
- #print 'delivering msg %d:' % portnum, `msg`
- self._ports[portnum].protocol.datagramReceived(msg, portnum)
- elif bytecnt == 0x1f:
- # modify command
- buf = self.peakBuffer(2)
- if buf is None:
- return False
- for k, (resp, extracnt) in self.receivedresp.iteritems():
- if (callable(resp) and resp(buf)) or buf == resp:
- if self.lenBuffer() < len(buf) + extracnt:
- return False
- self.consumeBuffer(len(buf))
- extra = self.getBuffer(extracnt)
- self.dispatch(k, portnum, extra)
- return True
- else:
- raise RuntimeError('unhandled msg: %s' % `''.join(self._buffer)`)
- else:
- # port receive data
- buf = self.peakBuffer(bytecnt + 1)
- if buf is None:
- return False
-
- #print 'f:', bytecnt, `buf`
- self.consumeBuffer(bytecnt + 1)
- if portnum in self._portqueue:
- self._portqueue[portnum].append(buf[1:])
-
- return True
-
- def dispatch(self, k, portnum, extra):
- '''Dispatch a message.'''
- #print 'dispatching: %s(%d, %s)' % (`k`, portnum, `extra`)
- getattr(self, 'R_%s' % k)(portnum, extra)
-
- # Messages
- def R_versionis(self, portnum, data):
- assert len(data) == 1
- self._version = bcdtoints(ord(data[0]))
- #print 'version:', `self._version`
-
- def R_portdisabled(self, portnum, data):
- if portnum == 7:
- assert not self._ports, \
- 'disabled, but ports present: %s' % `self._ports`
- return
-
- pobj = self._ports[portnum]
-
- del self._ports[portnum]
- del self._portqueue[portnum]
-
- # Signal that we are done
- pobj.protocol.doStop()
-
- def R_portenabled(self, portnum, data):
- if portnum not in self._portpending:
- #print 'skipping:', portnum
- return
-
- self._ports[portnum] = self._portpending[portnum]
- self._portqueue[portnum] = []
- del self._portpending[portnum]
- self._ports[portnum].protocol.makeConnection(self._ports[portnum])
-
- def R_slinkerror(self, portnum, data):
- pass
- #print 'got slinkerror on port:', `portnum`
-
- # Test Code
-
- class MyCDPlayer(CDPlayerProtocol):
- @staticmethod
- def printarg(args):
- print 'pa:', `args`
-
- def connectionMade(self):
- print 'power:', `self.transport.power`
- if not self.transport.power:
- print 'pon:', `self.transport.poweron()`
- print self.transport.discs()
- self.transport.trackinfo(32, 5).addCallback(
- self.printarg).addCallback(lambda x: self.transport.discinfo(32)).addCallback(self.printarg).addCallback(lambda x: self.transport.poweroff()).addCallback(lambda x: reactor.stop())
- #reactor.callLater(20, self.transport.poweroff)
-
- #print 'powered on, playing...'
- #self.transport.querydeckmodel()
- #self.transport.querystatus()
- #self.transport.querydiscinfo(5)
- #self.transport.querytrackinfo(5, 3)
- #self.transport.playtrack(10, 3)
-
- #print 'powered off...'
- #reactor.stop()
-
- def stateChanged(self):
- print 'sC'
-
- if __name__ == '__main__':
- if False:
- import serial
- s = serial.Serial('/dev/ttyU0', baudrate=38400, rtscts=True, timeout=5)
- s.write('\xff\xff')
- print 'reset'
- time.sleep(1)
-
- s.write(''.join(['\x02', '\x90"', '\x00']))
- print 'cmd'
- while True:
- print 'resp:', `s.read(1)`
- sys.exit(0)
-
- slinke = SLinkE()
- s = SerialPort(slinke, '/dev/ttyU0', reactor, baudrate=38400, rtscts=True)
- th = SLinkProtocol()
- slinke.openPort(0, th)
- th.connectCDPlayer(1, MyCDPlayer())
- #reactor.callLater(5, lambda: slinke.closePort(0))
- reactor.run()
|