#!/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 '' % ', '.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()