diff --git a/bellboy/actors/__init__.py b/bellboy/actors/__init__.py index d9c541ad..e69de29b 100644 --- a/bellboy/actors/__init__.py +++ b/bellboy/actors/__init__.py @@ -1,4 +0,0 @@ -import logging - - -log = logging.getLogger("actors") diff --git a/bellboy/actors/elevator.py b/bellboy/actors/elevator.py index a898c16c..94daa2af 100644 --- a/bellboy/actors/elevator.py +++ b/bellboy/actors/elevator.py @@ -4,11 +4,12 @@ class for the elevator and button related stuff. in actors for now.. could move """ -from actors import log +import logging + from collections import deque from utils.messages import SensorEvent, SensorEventMsg - +log = logging.getLogger("elevator") # simple "buttons", only have a position (depth) value and a radius.. all in cm BTN1_POS = 8.5 BTN2_POS = 16 diff --git a/bellboy/actors/generic.py b/bellboy/actors/generic.py index 6211adc4..d3c34a5d 100644 --- a/bellboy/actors/generic.py +++ b/bellboy/actors/generic.py @@ -1,6 +1,7 @@ +import logging +import os from abc import ABC, abstractmethod -from actors import log from thespian.actors import ActorAddress, ActorTypeDispatcher from utils.messages import Init, Response, StatusReq, SummaryReq, TestMode @@ -22,7 +23,7 @@ def __init__(self, *args, **kwargs): def nameOf(self, address: ActorAddress): """ Returns name of actor if it's entry exists in address book. - + Else returns the toString of the actorAddress. """ @@ -67,11 +68,17 @@ def receiveMsg_Init(self, message, sender): self._nameAddress(sender, message.senderName) if self.globalName is None: - log.warning("unnamed actor created!") - self.log = log.getChild(str(self.myAddress)) - else: - self.log = log.getChild(self.globalName) - self.log.info(str.format("{} created by {}", self.globalName, sender)) + self.globalName = str(self.myAddress) + + self.log = logging.getLogger(self.globalName) + self.log.info( + str.format( + "{} created by {}, pid={}", + self.globalName, + self.nameOf(sender), + os.getpid(), + ) + ) self.status = Response.READY self.send(sender, self.status) @@ -85,24 +92,28 @@ def receiveMsg_StatusReq(self, message: StatusReq, sender): """Sends a status update to sender.""" self.send(sender, self.status) - def receiveMsg_WakeupMessage(self, message: dict, sender: ActorAddress): - """On wakeup request.""" - self.send(sender, Response.AWAKE) - - @abstractmethod def receiveMsg_SummaryReq(self, message: SummaryReq, sender): """ - sends a summary of the actor to the sender. + Sends a summary of the actor to the sender. + """ + self.send(sender, self.summary()) + + def receiveMsg_ActorExitRequest(self, msg, sender): + """This is last msg processed before the Actor is shutdown.""" + self.log.debug("Received shutdown message!") + self.teardown() - to be defined in child classes. + @abstractmethod + def teardown(self): + """ + Actor's teardown sequence, called before shutdown. (i.e. close threads, disconnect from services, etc) """ pass - -# NOTE: -# If d actor isnt created by a bellboy actor, u must send an Init msg to it explicitly in order to use bellboy logs. -# This should only matter for the lead actor and during testing, -# cus all actors are sposed to be created thru bellboy lead anyways. -# Reason is bc the actors are running in independent processes, they dont share globals, i.e. the logger. -# Everything is communicated thru messaging. -# tldr the init msg holds the addressbook and log configs together + @abstractmethod + def summary(self): + """ + Returns a summary of the actor. The summary can be an object of any type described in the messages module. + :rtype: object + """ + pass diff --git a/bellboy/actors/lead.py b/bellboy/actors/lead.py index 637cbc4c..501c35bd 100644 --- a/bellboy/actors/lead.py +++ b/bellboy/actors/lead.py @@ -1,8 +1,19 @@ from actors.elevator import buttonHovered from actors.generic import GenericActor from actors.ultrasonic import UltrasonicActor +from actors.microphone import MicrophoneActor + from thespian.actors import ActorAddress -from utils.messages import Request, Response, SensorMsg, SensorReq, SensorResp +from utils.messages import ( + Request, + Response, + SensorMsg, + SensorReq, + SensorResp, + MicReq, + MicMsg, + MicResp, +) class BellboyLeadActor(GenericActor): @@ -16,8 +27,7 @@ def startBellboyLead(self): """ Starts bellboy lead actor services. - Configures global RPI Board. Spawns and sets up child actors - (ultrasonic sensor). + Spawns and sets up child actors """ self.log.info("Starting bellboy services.") @@ -26,12 +36,17 @@ def startBellboyLead(self): self.ultrasonic_sensor = self.createActor( UltrasonicActor, globalName="ultrasonic" ) + self.microphone = self.createActor(MicrophoneActor, globalName="microphone") - # request to setup sensor + # setup actors + # ultrasonic self.send( self.ultrasonic_sensor, SensorMsg(SensorReq.SETUP, trigPin=23, echoPin=24, maxDepth_cm=200), ) + + self.send(self.microphone, MicMsg(MicReq.SETUP, micNumber=0)) + self.status = Response.STARTED def stopBellboyLead(self): @@ -55,32 +70,24 @@ def receiveMsg_Request(self, message: Request, sender: ActorAddress): elif message is Request.STOP: self.stopBellboyLead() - elif message is Request.STATUS: - self.log.debug(str.format("Status check - {}", Response.ALIVE.name)) - - else: - msg = "Unhandled Request Enum value sent." - self.log.error(msg) - raise Exception(msg) - self.send(sender, self.status) + # handling sensor msgs def receiveMsg_SensorResp(self, message, sender): self.log.info( str.format("Received message {} from {}", message, self.nameOf(sender)) ) - # if bellboy is complete, we can ignore any response msgs. - if message == SensorResp.SET: - if sender == self.ultrasonic_sensor: - # sensor is setup and ready to go, lets start polling for a hovered button. - self.send( - sender, - SensorMsg( - SensorReq.POLL, pollPeriod_ms=100, triggerFunc=buttonHovered - ), - ) + # sensor is setup and ready to go, lets start polling for a hovered button. + self.send( + sender, + SensorMsg( + SensorReq.POLL, + pollPeriod_ms=100, + triggerFunc=buttonHovered, + ), + ) def receiveMsg_SensorEventMsg(self, message, sender): self.event_count += 1 @@ -101,7 +108,32 @@ def receiveMsg_SensorEventMsg(self, message, sender): self.log.debug("received 3 events, turning off sensor.") self.send(self.ultrasonic_sensor, SensorReq.STOP) - def receiveMsg_SummaryReq(self, message, sender): - """sends a summary of the actor.""" + # handling mic msgs + def receiveMsg_MicResp(self, message, sender): + self.log.info( + str.format("Received message {} from {}", message, self.nameOf(sender)) + ) + + if message == MicResp.SET: + self.send(sender, MicReq.START_LISTENING) + + def receiveMsg_MicEventMsg(self, message, sender): + self.log.info( + str.format("Received message {} from {}", message, self.nameOf(sender)) + ) + self.log.info( + str.format( + "{} event from {} - {}", + message.eventType, + self.nameOf(sender), + message.phraseHeard, + ) + ) + + def summary(self): + """Returns a summary of the actor.""" + return self.status # TODO flesh this out... - self.send(sender, self.status) + + def teardown(self): + pass diff --git a/bellboy/actors/microphone.py b/bellboy/actors/microphone.py new file mode 100644 index 00000000..08b92b1e --- /dev/null +++ b/bellboy/actors/microphone.py @@ -0,0 +1,130 @@ +import speech_recognition as sr +import time +from threading import Thread + +from actors.generic import GenericActor +from utils.messages import MicEvent, MicMsg, MicEventMsg, MicReq, MicResp, Response + +class MicrophoneActor(GenericActor): + """ + Class for the voice recognition microphone. nnn + """ + + def __init__(self): + super().__init__() + self.microphone = None + self.listening_thread = None + self.threadOn = False + self.recognizer = sr.Recognizer() + + def microphoneList(self): + """return list of microphones in the system""" + return self.sr.Microphone.list_microphone_names() + + # STATE METHODS + def setupMicrophone(self, micNumber): + """Choose system microphone to use as mic + + :param micNumber: microphone number as indexed by microphoneList() + :type micNumber: int + """ + self.micIx = micNumber + self.status = MicResp.SET + + def listening_loop(self): + """To run in mic's thread. Listens for speech.""" + + self.status = MicResp.LISTENING + timeout_sec = 30.0 + self.log.info("begun listening.") + while self.threadOn: + + # do the processing + with sr.Microphone(device_index=self.micIx) as source: + try: + audio = self.recognizer.listen(source, timeout=timeout_sec) + try: + recognized_audio = self.recognizer.recognize_google(audio) + self.log.info( + str.format("Someone said <<{}>>", recognized_audio) + ) + if "floor" in str(recognized_audio): + self.send( + self.parent, + MicEventMsg( + eventType=MicEvent.SPEECH_HEARD, + speechHeard=str(recognized_audio), + ), + ) + except sr.UnknownValueError: + self.log.debug("Google API: unknown speech heard") + + except speech_recognition.RequestError: + pass + # use sphinx instead + + + except sr.WaitTimeoutError: + self.log.debug( + str.format("Nothing was heard for {} seconds", timeout_sec) + ) + + self.log.info("Stopped listening thread") + self.status = MicResp.SET + + def start_listening(self): + if self.status != MicResp.SET: + self.log.warning("Mic not setup!") + return + + if self.status == MicResp.LISTENING: + self.log.info("Alreay listening!") + return + + self.threadOn = True + self.listening_thread = Thread(target=self.listening_loop) + self.listening_thread.start() + + def stop_listening(self): + if not self.threadOn: + self.log.info("Not listening") + return + + self.log.debug("Terminating listener thread") + self.threadOn = False + # join? + + # MSG HANDLING + def receiveMsg_MicMsg(self, msg, sender): + self.log.info( + str.format("Received message {} from {}", msg, self.nameOf(sender)) + ) + if msg.msgType == MicReq.SETUP: + self.setupMicrophone(msg.micNumber) + + if self.status != MicResp.SET: + self.send(sender, Response.FAIL) + + else: + self.send(sender, self.status) + + def receiveMsg_MicReq(self, msg, sender): + self.log.info( + str.format("Received message {} from {}", msg.name, self.nameOf(sender)) + ) + if msg == MicReq.GET_MIC_LIST: + self.send( + sender, MicMsg(msgType=MicResp.MIC_LIST, micList=microphoneList()) + ) + elif msg == MicReq.START_LISTENING: + self.start_listening() + + elif msg == MicReq.STOP_LISTENING: + self.stop_listening() + + # overrides + def summary(self): + return self.status + + def teardown(self): + self.stop_listening() diff --git a/bellboy/actors/ultrasonic.py b/bellboy/actors/ultrasonic.py index a7ffe371..5c49bd1a 100644 --- a/bellboy/actors/ultrasonic.py +++ b/bellboy/actors/ultrasonic.py @@ -55,6 +55,13 @@ def _setup_sensor(self, trigPin, echoPin, max_depth_cm): self._echoPin = echoPin self._max_depth_cm = max_depth_cm + if not self.TEST_MODE: + try: + import RPi # noqa + except ImportError: + self.log.warning("Not on RPI - Offtarget mode on") + self.TEST_MODE = True + if self.TEST_MODE: gpiozero.Device.pin_factory = MockFactory() # TODO should this be "globally" set in the test suites... @@ -76,20 +83,20 @@ def _sensor_loop(self): """ self.status = SensorResp.POLLING self._buffer.clear() - + self.log.info("starting sensor loop") while not self._terminate_thread: + t0 = time.time() - self._buffer.appendleft(self._sensor.distance * 100.0) + if not self.TEST_MODE: + self._buffer.appendleft(self._sensor.distance * 100.0) - # check for event, if occurred send msg to subscriber - event = self._eventFunc(self._buffer) - if event: - self.send(self.parent, event) + # check for event, if occurred send msg to subscriber + event = self._eventFunc(self._buffer) + if event: + self.send(self.parent, event) - # sleep till next period + # sleep till next period, o next mltiple of period dt_msec = (time.time() - t0) * MS_PER_SEC - # wait till next period if time took too long. - if dt_msec > self._poll_period: self.log.warning( str.format( @@ -107,7 +114,6 @@ def _begin_polling(self): """Begins running the polling thread.""" self._terminate_thread = False self._sensor_thread = Thread(target=self._sensor_loop) - self.log.info("starting sensor's thread") self._sensor_thread.start() self.status = SensorResp.POLLING @@ -118,7 +124,6 @@ def _stop_polling(self): self.log.debug("Stopping the UltraSonic detection loop...") self._terminate_thread = True - self.status = SensorResp.SET def _clear(self): self._trigPin = 0 @@ -196,14 +201,19 @@ def receiveMsg_SensorMsg(self, message: SensorMsg, sender): self.send(sender, self.status) - def receiveMsg_SummaryReq(self, message, sender): + # ----------# + # OVERRIDES # + # ----------# + + def teardown(self): + """ Sensor teardown, ensures polling thread is dead""" + self._stop_polling() + + def summary(self): """sends a summary of the actor.""" - self.send( - sender, - SensorMsg( - type=Response.SUMMARY, - trigPin=self._trigPin, - echoPin=self._echoPin, - maxDepth_cm=self._max_depth_cm, - ), + return SensorMsg( + type=Response.SUMMARY, + trigPin=self._trigPin, + echoPin=self._echoPin, + maxDepth_cm=self._max_depth_cm, ) diff --git a/bellboy/main.py b/bellboy/main.py index 1190388f..010827dd 100644 --- a/bellboy/main.py +++ b/bellboy/main.py @@ -3,21 +3,24 @@ from actors.lead import BellboyLeadActor from thespian.actors import ActorSystem -from utils.cli import configure_bellboy -from utils.messages import Init, Request, Response +from utils.cli import get_bellboy_configs +from utils.messages import Init, Request, Response, StatusReq def main(): """Starts the Bellboy system by creating an ActorSystem, creating the LeadActor, and asking it to START.""" - configure_bellboy() + configs = get_bellboy_configs() - log = logging.getLogger("Bellboy") + # Initialize the Actor system + system = ActorSystem(systemBase="multiprocQueueBase", logDefs=configs["logcfg"]) + + # for logging here in main + log = logging.getLogger("Main") log.info("Starting the Bellboy system") - # Initialize the Actor system - system = ActorSystem(systemBase="multiprocQueueBase") + # lead actor bellboy = system.createActor(BellboyLeadActor, globalName="bellboy_lead") status = system.ask(bellboy, Init()) @@ -32,12 +35,12 @@ def main(): while True: sleep(10) log.debug("Sending Heartbeat request to lead actor.") - system.tell(bellboy, Request.STATUS) + system.ask(bellboy, StatusReq()) except KeyboardInterrupt: log.error("The bellboy system was interrupted by the keyboard, exiting...") finally: - system.tell(bellboy, Request.STOP) + system.shutdown() if __name__ == "__main__": diff --git a/bellboy/tests/__init__.py b/bellboy/tests/__init__.py index e69de29b..02fb38c4 100644 --- a/bellboy/tests/__init__.py +++ b/bellboy/tests/__init__.py @@ -0,0 +1,35 @@ +import pytest +from thespian.actors import ActorSystem as ThespianActorSystem + + +@pytest.mark.skip(reason="actorsystem context manager, not a testing class") +class ActorSystem(object): + def __init__(self, systemBase: str, logcfg: dict): + self.test_system = ThespianActorSystem( + systemBase="multiprocQueueBase", logDefs=logcfg + ) + + def __enter__(self): + return self.test_system + + def __exit__(self, type, value, traceback): + self.test_system.shutdown() + + +logcfg = { + "version": 1, + "formatters": { + "standard": { + "format": "%(asctime)s :: %(levelname)s :: %(name)s :: %(funcName)s() :: %(message)s" + }, + }, + "handlers": { + "sh": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "formatter": "standard", + "level": "INFO", + }, + }, + "loggers": {"": {"handlers": ["sh"], "level": "INFO"}}, +} diff --git a/bellboy/tests/actors/test_lead.py b/bellboy/tests/actors/test_lead.py index 0445091f..2295d3b4 100644 --- a/bellboy/tests/actors/test_lead.py +++ b/bellboy/tests/actors/test_lead.py @@ -1,12 +1,13 @@ import time from actors.lead import BellboyLeadActor -from thespian.actors import ActorSystem +from tests import ActorSystem, logcfg from utils.messages import Init, Request, Response, TestMode -class TestLeadActor: - def test_receiveMsg_Requests(self, actor_system: ActorSystem): +def test_leadActor(): + + with ActorSystem("multiprocQueueBase", logcfg) as actor_system: lead = actor_system.createActor(BellboyLeadActor, globalName="test_lead") status = actor_system.ask(lead, Init(senderName="test_system")) assert status == Response.READY diff --git a/bellboy/tests/actors/test_ultrasonic.py b/bellboy/tests/actors/test_ultrasonic.py index a37acb75..517027ed 100644 --- a/bellboy/tests/actors/test_ultrasonic.py +++ b/bellboy/tests/actors/test_ultrasonic.py @@ -1,6 +1,8 @@ from actors.elevator import buttonHovered from actors.ultrasonic import UltrasonicActor -from thespian.actors import ActorSystem + +import time +from tests import ActorSystem, logcfg from utils.messages import ( Init, Response, @@ -13,10 +15,7 @@ ) -test_system = ActorSystem(systemBase="multiprocQueueBase") - - -def check_ultrasonicActor_setup(ultrasonic_actor): +def check_ultrasonicActor_setup(ultrasonic_actor, test_system): # ensure the ultrasonic sensor only accepts setup reqs from its parent test_trigPin = 10 test_echoPin = 11 @@ -47,9 +46,11 @@ def check_ultrasonicActor_setup(ultrasonic_actor): assert status == Response.READY -def check_ultrasonicActor_poll(ultrasonic_actor): +def check_ultrasonicActor_poll(ultrasonic_actor, test_system): + + pollperiod_ms = 100.0 poll_req = SensorMsg( - type=SensorReq.POLL, triggerFunc=buttonHovered, pollPeriod_ms=100.0 + type=SensorReq.POLL, triggerFunc=buttonHovered, pollPeriod_ms=pollperiod_ms ) setup_req = SensorMsg( type=SensorReq.SETUP, trigPin=10, echoPin=11, maxDepth_cm=100.0 @@ -73,8 +74,13 @@ def check_ultrasonicActor_poll(ultrasonic_actor): status = test_system.ask(ultrasonic_actor, StatusReq()) assert status == SensorResp.POLLING + time.sleep(3 * pollperiod_ms / 1000.0) + # stop polling status = test_system.tell(ultrasonic_actor, SensorReq.STOP) + + time.sleep(pollperiod_ms / 1000.0) + status = test_system.ask(ultrasonic_actor, StatusReq()) assert status == SensorResp.SET @@ -86,16 +92,17 @@ def check_ultrasonicActor_poll(ultrasonic_actor): # main test def test_ultrasonicActor(): - # create actor, ensure its ready to recieve messages - ultrasonic_actor = test_system.createActor( - UltrasonicActor, globalName="test_ultrasonic" - ) - ultrasonic_status = test_system.ask( - ultrasonic_actor, Init(senderName="test_system") - ) - assert ultrasonic_status == Response.READY - test_system.tell(ultrasonic_actor, TestMode()) - - # test behaviours - check_ultrasonicActor_setup(ultrasonic_actor) - check_ultrasonicActor_poll(ultrasonic_actor) + with ActorSystem("multiprocQueueBase", logcfg) as test_system: + # create actor, ensure its ready to recieve messages + ultrasonic_actor = test_system.createActor( + UltrasonicActor, globalName="test_ultrasonic" + ) + ultrasonic_status = test_system.ask( + ultrasonic_actor, Init(senderName="test_system") + ) + assert ultrasonic_status == Response.READY + test_system.tell(ultrasonic_actor, TestMode()) + + # test behaviours + check_ultrasonicActor_setup(ultrasonic_actor, test_system) + check_ultrasonicActor_poll(ultrasonic_actor, test_system) diff --git a/bellboy/utils/cli.py b/bellboy/utils/cli.py index 5727b6a4..929e83bd 100644 --- a/bellboy/utils/cli.py +++ b/bellboy/utils/cli.py @@ -1,53 +1,21 @@ """ -Setup of Bellboy run configurations, i.e. logging and run level. - -All command line processing here. +Bellboy command line argument processing goes here. """ import logging +import logging.config import os from argparse import ArgumentParser - -def configure_logging(log_level): - """ - configures logging globally through root logger. - - :param log_level: log verbosity - :type log_level: logging.LEVEL - """ - log_filename = "bellboy_log.txt" - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # create file logging handler & console logging handler - fh = logging.FileHandler(log_filename) - fh.setLevel(log_level) - ch = logging.StreamHandler() - ch.setLevel(log_level) - - # create formatter and add it to the handlers - formatter = logging.Formatter( - "%(asctime)s :: %(levelname)s :: %(name)s :: %(funcName)s() :: %(message)s" - ) - fh.setFormatter(formatter) - ch.setFormatter(formatter) - - # add the handlers to the logger - root_logger.addHandler(fh) - root_logger.addHandler(ch) - root_logger.info( - "Logging configured to console and {} at {} level".format( - os.path.abspath(log_filename), - logging.getLevelName(root_logger.getEffectiveLevel()), - ) - ) +log_filename = "bellboy.log" -# parse arguments +def get_bellboy_configs(): + """Retrieve bellboy configuration information from command line arguments. - -def configure_bellboy(): + :return: configs + :rtype: dict + """ # command line argument parsing parser = ArgumentParser( prog="python main.py", @@ -81,8 +49,41 @@ def configure_bellboy(): ) args = parser.parse_args() - # configure system to chosen settings. - configure_logging(args.log_level) + # using log config dictionary for actor logging, check thespian docs for more + configs = {} + configs["logcfg"] = { + "version": 1, + "formatters": { + "standard": { + "format": "%(asctime)s :: %(levelname)s :: %(name)s :: %(funcName)s() :: %(message)s" + }, + }, + "handlers": { + "fh": { + "class": "logging.FileHandler", + "filename": log_filename, + "formatter": "standard", + "level": args.log_level, + }, + "sh": { + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + "formatter": "standard", + "level": args.log_level, + }, + }, + "loggers": {"": {"handlers": ["fh", "sh"], "level": logging.DEBUG}}, + } + + configs["run_level"] = args.run_level + configs["log_level"] = args.log_level + logging.config.dictConfig(configs["logcfg"]) logging.getLogger().info( - f"Running at log level {args.log_level} and run level {args.run_level}" + str.format( + "Logging configured to console and {} at {} level", + os.path.abspath(log_filename), + logging.getLevelName(logging.getLevelName(args.log_level)), + ) ) + + return configs diff --git a/bellboy/utils/messages.py b/bellboy/utils/messages.py index e29c3e85..5beb3490 100644 --- a/bellboy/utils/messages.py +++ b/bellboy/utils/messages.py @@ -27,7 +27,7 @@ class TestMode: # general requests class Request(Enum): - START, STOP, STATUS, CLEAR = range(4) + START, STOP, CLEAR = range(3) # general responses @@ -90,9 +90,37 @@ class SensorEvent(Enum): # for event with more info class SensorEventMsg: - def __init__(self, eventType, eventData): + def __init__(self, eventType: SensorEvent, eventData): self.eventType = eventType self.eventData = eventData def __str__(self): return self.eventType.name + + +"""Microphone messages""" +class MicReq(Enum): + SETUP, GET_MIC_LIST, START_LISTENING, STOP_LISTENING = range(4) + +class MicResp(Enum): + SET, LISTENING, MIC_LIST= range(3) + +class MicMsg: + def __init__(self, msgType, micNumber=None): + self.msgType = msgType + self.micNumber = micNumber + + def __str__(self): + return self.msgType.name + +class MicEvent(Enum): + FLOOR_CHOSEN, SPEECH_HEARD = range(2) + +class MicEventMsg: + def __init__(self, eventType: MicEvent, floorChoice=0, phraseHeard=None): + self.eventType = eventType + self.floorChoice = floorChoice + self.phraseHeard = phraseHeard + + def __str__(self): + return self.eventType.name \ No newline at end of file diff --git a/experiments/mic_check.py b/experiments/mic_check.py new file mode 100644 index 00000000..b4627bb4 --- /dev/null +++ b/experiments/mic_check.py @@ -0,0 +1,46 @@ + +# import pyaudio +# p = pyaudio.PyAudio() +# for i in range(p.get_device_count()): +# dev = p.get_device_info_by_index(i) +# print((i,dev['name'],dev['maxInputChannels'])) + +import pyaudio +import wave + +form_1 = pyaudio.paInt16 # 16-bit resolution +chans = 1 # 1 channel +samp_rate = 44100 # 44.1kHz sampling rate +chunk = 4096 # 2^12 samples for buffer +record_secs = 3 # seconds to record +dev_index = 0 # device index found by p.get_device_info_by_index(i) +wav_output_filename = 'test1.wav' # name of .wav file + +audio = pyaudio.PyAudio() # create pyaudio instantiation + +# create pyaudio stream +stream = audio.open(format = form_1,rate = samp_rate,channels = chans, \ + input_device_index = dev_index,input = True, \ + frames_per_buffer=chunk) +print("recording") +frames = [] + +# loop through stream and append audio chunks to frame array +for ii in range(0,int((samp_rate/chunk)*record_secs)): + data = stream.read(chunk) + frames.append(data) + +print("finished recording") + +# stop the stream, close it, and terminate the pyaudio instantiation +stream.stop_stream() +stream.close() +audio.terminate() + +# save the audio frames as .wav file +wavefile = wave.open(wav_output_filename,'wb') +wavefile.setnchannels(chans) +wavefile.setsampwidth(audio.get_sample_size(form_1)) +wavefile.setframerate(samp_rate) +wavefile.writeframes(b''.join(frames)) +wavefile.close() \ No newline at end of file diff --git a/experiments/speech_recog.py b/experiments/speech_recog.py new file mode 100644 index 00000000..af22e207 --- /dev/null +++ b/experiments/speech_recog.py @@ -0,0 +1,47 @@ +# Using uberi speech_recognition +# pip install SpeechRecognition +# requires pyaudio. on windows I had to "pipwin install pyaudio" bc pyaudio not compatible w python3.7 + +# pocket sphinx for local speech recognition +# need to download swig for c/c++ support (?). add swig to ur Path +# python -m pip install --upgrade pip setuptools wheel +# pip install --upgrade pocketsphinx + +# google cloud for cloud pip install google-cloud-speech +# + +# microphone behaviour +# 1. setup microphone (choose grammar and mic and sens or wtv) +# 2. listen for events +# 3. +# from actors.generic import GenericActor +import speech_recognition as sr + +r = sr.Recognizer() +with sr.Microphone() as source: + print("Say something!") + audio = r.listen(source) + +# # recognize speech using Sphinx +# try: +# print("Sphinx thinks you said " + r.recognize_sphinx(audio)) +# except sr.UnknownValueError: +# print("Sphinx could not understand audio") +# except sr.RequestError as e: +# print("Sphinx error; {0}".format(e)) + + +# recognize speech using Google Speech Recognition +try: + # for testing purposes, we're just using the default API key + # to use another API key, use `r.recognize_google(audio, key="GOOGLE_SPEECH_RECOGNITION_API_KEY")` + # instead of `r.recognize_google(audio)` + print("Google Speech Recognition thinks you said " + r.recognize_google(audio)) +except sr.UnknownValueError: + print("Google Speech Recognition could not understand audio") +except sr.RequestError as e: + print( + "Could not request results from Google Speech Recognition service; {0}".format( + e + ) + ) diff --git a/experiments/test1.wav b/experiments/test1.wav new file mode 100644 index 00000000..829d443f Binary files /dev/null and b/experiments/test1.wav differ