parent
efe8685371
commit
abd4301390
|
@ -0,0 +1,5 @@
|
||||||
|
*.py[co]
|
||||||
|
*~
|
||||||
|
stations.conf
|
||||||
|
recordings/*
|
||||||
|
sounds/*
|
|
@ -1,7 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from Queue import Queue
|
||||||
from twisted.internet.protocol import ClientFactory
|
from twisted.internet.protocol import ClientFactory
|
||||||
from twisted.protocols.basic import LineReceiver
|
from twisted.protocols.basic import LineReceiver
|
||||||
|
from twisted.internet.task import LoopingCall
|
||||||
|
from twisted.python import log
|
||||||
from frn.utility import *
|
from frn.utility import *
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +13,7 @@ class FRNClient(LineReceiver):
|
||||||
client_version = 2010002
|
client_version = 2010002
|
||||||
|
|
||||||
def connectionMade(self):
|
def connectionMade(self):
|
||||||
|
self.txq = Queue()
|
||||||
self.login()
|
self.login()
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
@ -34,7 +38,7 @@ class FRNClient(LineReceiver):
|
||||||
self.msgbuffer.append(line)
|
self.msgbuffer.append(line)
|
||||||
self.phase += 1
|
self.phase += 1
|
||||||
if self.phase >= self.expected_lines:
|
if self.phase >= self.expected_lines:
|
||||||
handler = getattr(self, 'on_'+self.status, self.on_unimplemented)
|
handler = getattr(self, 'decode'+self.status, self.unimplemented)
|
||||||
message = self.msgbuffer
|
message = self.msgbuffer
|
||||||
self.ready()
|
self.ready()
|
||||||
handler(message)
|
handler(message)
|
||||||
|
@ -54,7 +58,7 @@ class FRNClient(LineReceiver):
|
||||||
audio_data = self.msgbuffer
|
audio_data = self.msgbuffer
|
||||||
source = ord(audio_data[0])*256+ord(audio_data[1])
|
source = ord(audio_data[0])*256+ord(audio_data[1])
|
||||||
self.ready()
|
self.ready()
|
||||||
self.on_AUDIO(source, audio_data[2:])
|
self.decodeAUDIO(source, audio_data[2:])
|
||||||
if len(data) > needed:
|
if len(data) > needed:
|
||||||
self.dataReceived(data[needed:])
|
self.dataReceived(data[needed:])
|
||||||
|
|
||||||
|
@ -65,7 +69,7 @@ class FRNClient(LineReceiver):
|
||||||
self.phase = 1
|
self.phase = 1
|
||||||
else:
|
else:
|
||||||
self.serverdata = parseSimpleXML(line.strip())
|
self.serverdata = parseSimpleXML(line.strip())
|
||||||
print self.serverdata
|
self.loginResponse(self.serverdata)
|
||||||
if int(self.serverdata['sv']) > 2009004:
|
if int(self.serverdata['sv']) > 2009004:
|
||||||
self.sendLine(makeAuthKey(self.serverdata['kp']))
|
self.sendLine(makeAuthKey(self.serverdata['kp']))
|
||||||
self.ready()
|
self.ready()
|
||||||
|
@ -76,9 +80,9 @@ class FRNClient(LineReceiver):
|
||||||
if self.status == 'READY':
|
if self.status == 'READY':
|
||||||
packet_type = ord(data[0])
|
packet_type = ord(data[0])
|
||||||
if packet_type == 0: # Keepalive
|
if packet_type == 0: # Keepalive
|
||||||
self.sendLine('P')
|
self.pong()
|
||||||
elif packet_type == 1: # TX ack
|
elif packet_type == 1: # TX ack
|
||||||
self.status == 'TX'
|
self.status = 'TX'
|
||||||
self.phase = 0
|
self.phase = 0
|
||||||
if len(data) > 1:
|
if len(data) > 1:
|
||||||
self.dataReceived(data[1:])
|
self.dataReceived(data[1:])
|
||||||
|
@ -86,18 +90,21 @@ class FRNClient(LineReceiver):
|
||||||
self.startAudioMessage(data[1:])
|
self.startAudioMessage(data[1:])
|
||||||
elif packet_type == 3: # Client list
|
elif packet_type == 3: # Client list
|
||||||
self.startMultiLineMessage('CLIENTS', data[1:])
|
self.startMultiLineMessage('CLIENTS', data[1:])
|
||||||
elif packet_type == 4: # SMS
|
elif packet_type == 4: # Text
|
||||||
self.startMultiLineMessage('SMS', data[1:])
|
self.startMultiLineMessage('TEXT', data[1:])
|
||||||
elif packet_type == 5: # Channel list
|
elif packet_type == 5: # Channel list
|
||||||
self.startMultiLineMessage('CHANNELS', data[1:])
|
self.startMultiLineMessage('NETWORKS', data[1:])
|
||||||
else:
|
else:
|
||||||
print "Unknown packet type %d" % packet_type
|
log.err("Unknown packet type %d" % packet_type)
|
||||||
elif self.status == 'AUDIO':
|
elif self.status == 'AUDIO':
|
||||||
self.collectAudioMessage(data)
|
self.collectAudioMessage(data)
|
||||||
elif self.status == 'TX':
|
elif self.status == 'TX':
|
||||||
if self.phase == 0:
|
if self.phase == 0:
|
||||||
self.phase = 1
|
self.phase = 1
|
||||||
self.on_TX(ord(data[0])*256+ord(data[1]))
|
self.decodeTX(ord(data[0])*256+ord(data[1]))
|
||||||
|
self.ready()
|
||||||
|
if len(data) > 2:
|
||||||
|
self.dataReceived(data[2:])
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
d = self.factory.client_id
|
d = self.factory.client_id
|
||||||
|
@ -116,31 +123,82 @@ class FRNClient(LineReceiver):
|
||||||
self.status = 'AUTH'
|
self.status = 'AUTH'
|
||||||
self.phase = 0
|
self.phase = 0
|
||||||
self.sendLine(ap)
|
self.sendLine(ap)
|
||||||
#self.request_rx()
|
|
||||||
|
|
||||||
def set_status(self, status):
|
def pong(self):
|
||||||
|
self.sendLine('P')
|
||||||
|
|
||||||
|
def setStatus(self, status):
|
||||||
self.sendLine('ST:%s' % str(status))
|
self.sendLine('ST:%s' % str(status))
|
||||||
|
|
||||||
def request_rx(self):
|
def stopTransmission(self):
|
||||||
self.sendLine('RX0')
|
self.sendLine('RX0')
|
||||||
|
|
||||||
def request_tx(self):
|
def startTransmission(self):
|
||||||
self.sendLine('TX0')
|
self.sendLine('TX0')
|
||||||
|
|
||||||
def send_audio(self, frame):
|
def sendAudioFrame(self, frame):
|
||||||
self.sendLine('TX1')
|
self.sendLine('TX1')
|
||||||
self.transport.write(frame)
|
self.transport.write(frame)
|
||||||
|
|
||||||
def send_SMS(self, dest, text):
|
def streamStep(self, count):
|
||||||
|
if count > 1:
|
||||||
|
log.msg("WARNING: lost %d ticks" % (count-1))
|
||||||
|
for i in range(count):
|
||||||
|
self.sendAudioFrame(self.txq.get_nowait())
|
||||||
|
|
||||||
|
def stopStreaming(self):
|
||||||
|
self.txtimer.stop()
|
||||||
|
|
||||||
|
def _streamAck(self):
|
||||||
|
self.txtimer = LoopingCall.withCount(self.streamStep)
|
||||||
|
self.txtimer.start(0.20).addCallback(
|
||||||
|
lambda _: self.stopTransmission()).addErrback(
|
||||||
|
lambda _: self.stopTransmission())
|
||||||
|
|
||||||
|
def feedStreaming(self, frames):
|
||||||
|
if type(frames) == list:
|
||||||
|
for frame in frames:
|
||||||
|
self.txq.put_nowait(frame)
|
||||||
|
else:
|
||||||
|
self.txq.put_nowait(frames)
|
||||||
|
|
||||||
|
def startStreaming(self):
|
||||||
|
self.startTransmission()
|
||||||
|
|
||||||
|
def sendTextMessage(self, dest, text):
|
||||||
self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text)))
|
self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text)))
|
||||||
|
|
||||||
def on_unimplemented(self, msg):
|
def unimplemented(self, msg):
|
||||||
print msg
|
log.msg("Unimplemented: %s" % msg)
|
||||||
|
|
||||||
def on_AUDIO(self, from_id, frames):
|
def decodeAUDIO(self, from_id, frames):
|
||||||
|
self.audioFrameReceived(from_id, frames)
|
||||||
|
|
||||||
|
def decodeTX(self, my_id):
|
||||||
|
self._streamAck()
|
||||||
|
|
||||||
|
def decodeTEXT(self, msg):
|
||||||
|
self.textMessageReceived(msg[0], msg[1], msg[2])
|
||||||
|
|
||||||
|
def decodeCLIENTS(self, msg):
|
||||||
|
self.clientsListUpdated([parseSimpleXML(x) for x in msg])
|
||||||
|
|
||||||
|
def decodeNETWORKS(self, msg):
|
||||||
|
self.networksListUpdated(msg)
|
||||||
|
|
||||||
|
def loginResponse(self, info):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_TX(self, my_id):
|
def audioFrameReceived(self, from_id, frame):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def textMessageReceived(self, client, message, target):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clientsListUpdated(self, clients):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def networksListUpdated(self, networks):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,16 +210,16 @@ class FRNClientFactory(ClientFactory):
|
||||||
self.client_id = kw
|
self.client_id = kw
|
||||||
|
|
||||||
def startedConnecting(self, connector):
|
def startedConnecting(self, connector):
|
||||||
print 'Started to connect.'
|
log.msg('Started to connect')
|
||||||
|
|
||||||
def buildProtocol(self, addr):
|
def buildProtocol(self, addr):
|
||||||
print 'Connected.'
|
log.msg('Connected')
|
||||||
return ClientFactory.buildProtocol(self, addr)
|
return ClientFactory.buildProtocol(self, addr)
|
||||||
|
|
||||||
def clientConnectionLost(self, connector, reason):
|
def clientConnectionLost(self, connector, reason):
|
||||||
print 'Lost connection. Reason:', reason
|
log.msg('Lost connection. Reason: %s' % reason)
|
||||||
|
|
||||||
def clientConnectionFailed(self, connector, reason):
|
def clientConnectionFailed(self, connector, reason):
|
||||||
print 'Connection failed. Reason:', reason
|
log.err('Connection failed. Reason: %s' % reason)
|
||||||
|
|
||||||
# vim: set et ai sw=4 ts=4 sts=4:
|
# vim: set et ai sw=4 ts=4 sts=4:
|
||||||
|
|
|
@ -3,29 +3,98 @@
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
from frn.protocol.client import FRNClient, FRNClientFactory
|
from frn.protocol.client import FRNClient, FRNClientFactory
|
||||||
|
from twisted.internet import reactor, task
|
||||||
|
from twisted.internet.defer import DeferredList
|
||||||
|
from twisted.python import log
|
||||||
|
import os, string
|
||||||
|
safe_chars = string.ascii_letters+string.digits+' :;.,+-=$@'
|
||||||
|
|
||||||
|
PARROT_AUDIO_DELAY = 5.0
|
||||||
|
|
||||||
|
def sanitizeFilename(name):
|
||||||
|
r = ''
|
||||||
|
for c in name:
|
||||||
|
if c in safe_chars:
|
||||||
|
r += c
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
class FRNParrot(FRNClient):
|
class FRNParrot(FRNClient):
|
||||||
|
|
||||||
def on_SMS(self, msg):
|
def getClientName(self, client_id):
|
||||||
print "Messaggio %s da %s: %s" % (msg[2], msg[0], msg[1])
|
if self.clientsById.has_key(client_id):
|
||||||
if msg[2][0] == 'P': # Risponde solo ai messaggi privati
|
return self.clientsById[client_id]['on']
|
||||||
self.send_SMS(msg[0], msg[1])
|
else:
|
||||||
|
return client_id
|
||||||
|
|
||||||
def on_AUDIO(self, from_id, frames):
|
def textMessageReceived(self, client, message, target):
|
||||||
print "AUDIO from %d (%d bytes)" % (from_id, len(frames))
|
log.msg("Type %s message from %s: %s" %
|
||||||
with file('rx-%d.gsm' % from_id, 'ab') as f:
|
(target, self.getClientName(client), message))
|
||||||
|
if target == 'P': # Only reply to private messages
|
||||||
|
if not message.startswith('play'):
|
||||||
|
self.sendTextMessage(client, message)
|
||||||
|
else:
|
||||||
|
cmd = message.split()
|
||||||
|
if len(cmd) > 1:
|
||||||
|
message = sanitizeFilename(cmd[1])
|
||||||
|
else:
|
||||||
|
message = 'monkeys'
|
||||||
|
filename = 'sounds/%s.wav' % message
|
||||||
|
if os.path.exists(filename):
|
||||||
|
log.msg("Streaming file %s" % filename)
|
||||||
|
with file(filename, 'rb') as sf:
|
||||||
|
sf.seek(0x3c) # Skip wav header
|
||||||
|
while True:
|
||||||
|
b = sf.read(325)
|
||||||
|
if len(b) < 325:
|
||||||
|
break
|
||||||
|
self.feedStreaming(b)
|
||||||
|
self.factory.reactor.callLater(0.5,
|
||||||
|
self.startStreaming)
|
||||||
|
else:
|
||||||
|
self.sendTextMessage(client, "File not found")
|
||||||
|
|
||||||
|
def stopTransmission(self):
|
||||||
|
FRNClient.stopTransmission(self)
|
||||||
|
log.msg("Stopped playback.")
|
||||||
|
|
||||||
|
def startRepeating(self, from_id):
|
||||||
|
log.msg("%s stopped talking: starting playback." %
|
||||||
|
self.clients[from_id-1]['on'])
|
||||||
|
self.startStreaming()
|
||||||
|
|
||||||
|
def audioFrameReceived(self, from_id, frames):
|
||||||
|
recname = sanitizeFilename(self.clients[from_id-1]['on'])
|
||||||
|
with file('recordings/%s.gsm' % recname, 'ab') as f:
|
||||||
f.write(frames)
|
f.write(frames)
|
||||||
|
self.feedStreaming(frames)
|
||||||
|
try:
|
||||||
|
self.parrot_timer.reset(PARROT_AUDIO_DELAY)
|
||||||
|
except:
|
||||||
|
log.msg("%s started talking" %
|
||||||
|
self.clients[from_id-1]['on'])
|
||||||
|
self.parrot_timer = self.factory.reactor.callLater(
|
||||||
|
PARROT_AUDIO_DELAY, self.startRepeating, from_id)
|
||||||
|
self.pong()
|
||||||
|
|
||||||
|
def loginResponse(self, info):
|
||||||
|
log.msg("Login: %s" % info['al'])
|
||||||
|
|
||||||
|
def clientsListUpdated(self, clients):
|
||||||
|
self.clients = clients
|
||||||
|
self.clientsById = dict([(i['id'], i) for i in clients])
|
||||||
|
|
||||||
|
|
||||||
class FRNParrotFactory(FRNClientFactory):
|
class FRNParrotFactory(FRNClientFactory):
|
||||||
protocol = FRNParrot
|
protocol = FRNParrot
|
||||||
|
reactor = reactor
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
from twisted.internet import reactor
|
|
||||||
|
log.startLogging(sys.stderr)
|
||||||
|
|
||||||
cfg = ConfigParser()
|
cfg = ConfigParser()
|
||||||
cfg.read('stations.conf')
|
cfg.read('stations.conf')
|
||||||
|
@ -36,7 +105,7 @@ if __name__ == '__main__':
|
||||||
stations = cfg.sections()
|
stations = cfg.sections()
|
||||||
stations.sort()
|
stations.sort()
|
||||||
station = cfg.sections()[0]
|
station = cfg.sections()[0]
|
||||||
print "Profile not specified: using '%s'" % station
|
log.msg("Profile not specified: using '%s'" % station)
|
||||||
|
|
||||||
station_conf = dict(cfg.items(station))
|
station_conf = dict(cfg.items(station))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue