commit e2e4c072f851458a0d2328924ae1e2e6d36130b8 Author: Ruben van de Ven Date: Tue Jan 15 21:40:44 2019 +0100 Initial WIP version diff --git a/hugvey/__init__.py b/hugvey/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hugvey/__pycache__/__init__.cpython-36.pyc b/hugvey/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..b34ad43 Binary files /dev/null and b/hugvey/__pycache__/__init__.cpython-36.pyc differ diff --git a/hugvey/__pycache__/client.cpython-36.pyc b/hugvey/__pycache__/client.cpython-36.pyc new file mode 100644 index 0000000..4250ada Binary files /dev/null and b/hugvey/__pycache__/client.cpython-36.pyc differ diff --git a/hugvey/__pycache__/communication.cpython-36.pyc b/hugvey/__pycache__/communication.cpython-36.pyc new file mode 100644 index 0000000..c37ea72 Binary files /dev/null and b/hugvey/__pycache__/communication.cpython-36.pyc differ diff --git a/hugvey/client.py b/hugvey/client.py new file mode 100644 index 0000000..4cc907a --- /dev/null +++ b/hugvey/client.py @@ -0,0 +1,178 @@ +import pyaudio +import socket +import select +import audioop +import threading +import logging +import time +import zmq +import asyncio +from zmq.asyncio import Context +from .communication import zmqReceive, zmqSend, getTopic + +logger = logging.getLogger("client") + +class VoiceServer(object): + """A UDP server, providing mic data at 16 kHz""" + def __init__(self, voice_port, input_rate, input_name = None, target_rate = 16000): + self.voice_port = voice_port + self.input_rate = input_rate + self.target_rate = target_rate + self.stopped = True + self.clients = [] + self.laststate = None + self.input_name = input_name + + def get_input_idx(self): + input_device_idx = None + # input_device_idx = 6 + # input_device_idx = 0 + devices_count = self.p.get_device_count() + for i in range(devices_count): + dev = self.p.get_device_info_by_index(i) + if input_device_idx is None and dev['maxInputChannels'] > 0: + if (self.input_name and self.input_name in dev['name']) or \ + (not self.input_name and dev['name'] != 'default'): + input_device_idx = dev['index'] + logger.info("Use device {0}: {1}".format(dev['index'],dev['name'])) + logger.debug("{} {:0d} {}".format("* " if input_device_idx == i else "- ", i, dev['name'])) + return input_device_idx + + + def onBuffer(self, in_data, frame_count, time_info, status): + if self.input_rate == self.target_rate: + f = in_data + else: + f, self.laststate = audioop.ratecv(in_data, 2, 1, self.input_rate, self.target_rate, self.laststate) + + for s in self.clients: + try: + s.send(f) + except Exception as e: + self.clients.remove(s) + logger.warn("Error sending to {}".format(s.getsockname())) + pass + return (None, pyaudio.paContinue) + + async def start(self): + FORMAT = pyaudio.paInt16 + CHANNELS = 1 + CHUNK = 4096 + + self.p = pyaudio.PyAudio() + self.stopped = False + + stream = self.p.open( + format=FORMAT, + channels=CHANNELS, + rate=self.input_rate, + input=True, + frames_per_buffer=CHUNK, + stream_callback=self.onBuffer, + input_device_index=self.get_input_idx() + ) + + while not self.stopped: + try: + self.voice_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.voice_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.voice_socket.bind(('', self.voice_port)) + self.voice_socket.listen(5) + + read_list = [self.voice_socket] + logger.info( "Waiting for connections") + while not self.stopped: + (clientsocket, address) = self.voice_socket.accept() + self.clients.append(clientsocket) + + logger.info( "Stop recording & streaming") + self.voice_socket.close() + # stop Recording + stream.stop_stream() + stream.close() + self.p.terminate() + except Exception as e: + logging.critical("Socket Exception {}".format(e)) + self.voice_socket.close() + time.sleep(.5) + + def stop(self): + self.stopped = True + +class CommandHandler(object): + def __init__(self, hugvey_id, event_address = "tcp://127.0.0.1:5555"): + self.eventQueue = [] + self.ctx = Context.instance() + self.hugvey_id = hugvey_id + self.event_address = event_address + + def handle(self, cmd): + if not 'action' in cmd: + logger.critical("Invalid command: {}".format(cmd)) + return + + logger.info("Received {}".format(cmd)) + if cmd['action'] == 'play': + self.cmdPlay(cmd['id'], cmd['msg']) + + def cmdPlay(self, msgId, msgText): + # espeak(msgText) + logger.inof("Play: {}".format(msgText)) + time.sleep(2) + sendMessage({ + 'event': 'playbackFinish', + 'msgId': msgId + }) + + def sendMessage(self, msg): + self.eventQueue.append(msg) + + async def command_listener(self): + s = self.ctx.socket(zmq.SUB) + s.connect(self.event_address) + queueName = 'hv{}'.format(self.hugvey_id) + s.subscribe(queueName) + logger.info("Subscribed to commands on {}".format(queueName)) + while True: + hugvey_id, msg = await zmqReceive(s) + # topic, msg = await s.recv_multipart() + print('received', msg) + s.close() + + async def event_sender(port): + s = self.ctx.socket(zmq.PUB) + s.connect(self.event_send_address) + topic = getTopic(self.hugvey_id) + s.subscribe(topic) + + logger.info("Subscribed to commands on {}".format(topic)) + while True: + for i in range(len(self.eventQueue)): + hugvey_id, msg = await zmqSend(s, self.hugvey_id, self.eventQueue.pop(0)) + if len(self.eventQueue) == 0: + await asyncio.sleep(0.05) + + s.close() + +class Hugvey(object): + """The Hugvey client, to be ran on the Raspberry Pi's + """ + def __init__(self): + pass + + def loadConfig(self, filename): + # filename + pass + + async def startCommandListener(): + return await self.cmd_server.command_listener() + + def start(self): + self.voice_server = VoiceServer(4444, 44100) + self.cmd_server = CommandHandler(1) + loop = asyncio.get_event_loop() + logger.info('start') + loop.run_until_complete(self.voice_server.start()) + loop.run_until_complete(self.cmd_server.command_listener()) + loop.run_until_complete(self.cmd_server.command_listener()) + logger.info('done') diff --git a/hugvey/communication.py b/hugvey/communication.py new file mode 100644 index 0000000..d9a3eed --- /dev/null +++ b/hugvey/communication.py @@ -0,0 +1,20 @@ +import json +import logging + +logger = logging.getLogger("communication") + +def getTopic(hugvey_id): + return "hv{}".format(hugvey_id) + + +def zmqSend(socket, hugvey_id, msg): + msgData = json.dumps(msg) + topic = getTopic(hugvey_id) + logger.info("Send 0mq to {} containing {}".format(topic, msg)) + return socket.send_multipart([topic.encode(), msgData.encode()]) + +async def zmqReceive(socket): + topic, msg = await socket.recv_multipart() + hugvey_id = topic.decode()[2:] + logger.info("Received 0mq messages for Hugvey #{} containing {}".format(hugvey_id, msg.decode())) + return hugvey_id, json.loads(msg) diff --git a/hugvey_client.py b/hugvey_client.py new file mode 100644 index 0000000..4a3ca89 --- /dev/null +++ b/hugvey_client.py @@ -0,0 +1,47 @@ +from hugvey.client import Hugvey +import coloredlogs, logging +import argparse + + + + +if __name__ == '__main__': + argParser = argparse.ArgumentParser(description='Start up a Hugvey pillow. Mic stream becomes available on TCP Socket, and starts listening + emitting events') + # argParser.add_argument( + # '--voice-port', + # required=True, + # type=int, + # help='The port on which to listen for TCP connections (listens on 0.0.0.0) for audio receivers' + # ) + # argParser.add_argument( + # '--event-address', + # type=str, + # default="127.0.0.1", + # help='The ip to which to set up the TCP connection for sending events. Can also be an existing unix file socket.' + # ) + # argParser.add_argument( + # '--event-port', + # type=int, + # help='The port on which to set up the TCP connection for sending events. Ignored if --event-address points to a file socket' + # ) + # argParser.add_argument( + # '--language-code', + # default="en-US", + # type=str, + # help='Language code for Speech to Text (BCP-47 language tag)' + # ) + argParser.add_argument( + '--verbose', + '-v', + action="store_true", + ) + + args = argParser.parse_args() + + coloredlogs.install( + level=logging.DEBUG if args.verbose else logging.INFO, + ) + + # server = VoiceServer(voice_port=4444, input_rate=44100) + hv = Hugvey() + hv.start() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d4002e1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pyzmq +pyaudio +coloredlogs