Streaming audio to DialogFlow for real-time intent recognition - google-cloud-platform

I'm trying to stream audio from a (Pepper robot) microphone to DialogFlow. I have working code for sending a block of audio. When I send the request, the response contains the message None Exception iterating requests!. I've seen this error previously when I was reading from an audio file. However, I fail to see what's wrong with the data I'm passing now.
processRemote is called whenever the microphone records something. When writing the sound_data[0].tostring() to a StringIO and later retrieving it in chunks of 4096 bytes, the solution works.
self.processing_queue is supposed to hold a few chunks of audio that should be processed before working on new audio.
The error occurs in the response for self.session_client.streaming_detect_intent(requests).
I'm thankful for any idea.
def processRemote(self, nbOfChannels, nbOfSamplesByChannel, timeStamp, inputBuffer):
"""audio stream callback method with simple silence detection"""
sound_data_interlaced = np.fromstring(str(inputBuffer), dtype=np.int16)
sound_data = np.reshape(sound_data_interlaced,
(nbOfChannels, nbOfSamplesByChannel), 'F')
peak_value = np.max(sound_data)
chunk = sound_data[0].tostring()
self.processing_queue.append(chunk)
if self.is_active:
# detect sound
if peak_value > 6000:
print("Peak:", peak_value)
if not self.recordingInProgress:
self.startRecording()
# if recording is in progress we send directly to google
try:
if self.recordingInProgress:
print("preparing request proc remote")
requests = [dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk)]
print("should send now")
responses = self.session_client.streaming_detect_intent(requests)
for response in responses:
print("checking response")
if len(response.fulfillment_text) != 0:
print("response not empty")
self.stopRecording(response) # stop if we already know the intent
except Exception as e:
print(e)
def startRecording(self):
"""init a in memory file object and save the last raw sound buffer to it."""
# session path setup
self.session_path = self.session_client.session_path(DIALOG_FLOW_GCP_PROJECT_ID, self.uuid)
self.recordingInProgress = True
requests = list()
# set up streaming
print("start streaming")
q_input = dialogflow.types.QueryInput(audio_config=self.audio_config)
req = dialogflow.types.StreamingDetectIntentRequest(
session=self.session_path, query_input=q_input)
requests.append(req)
# process pre-recorded audio
print("work on stored audio")
for chunk in self.processing_queue:
print("appending chunk")
try:
requests.append(dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk))
except Exception as e:
print(e)
print("getting response")
responses = self.session_client.streaming_detect_intent(requests)
print("got response")
print(responses)
# iterate though responses from pre-recorded audio
try:
for response in responses:
print("checking response")
if len(response.fulfillment_text) != 0:
print("response not empty")
self.stopRecording(response) # stop if we already know the intent
except Exception as e:
print(e)
# otherwise continue listening
print("start recording (live)")
def stopRecording(self, query_result):
"""saves the recording to memory"""
# stop recording
self.recordingInProgress = False
self.disable_google_speech(force=True)
print("stopped recording")
# process response
action = query_result.action
text = query_result.fulfillment_text.encode("utf-8")
if (action is not None) or (text is not None):
if len(text) != 0:
self.speech.say(text)
if len(action) != 0:
parameters = query_result.parameters
self.execute_action(action, parameters)

As per the source code the session_client.streaming_detect_intent function expects an iterable as its argument. But you are currently giving it a list of requests.
Won't work:
requests = [dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk)]
responses = self.session_client.streaming_detect_intent(requests)
#None Exception iterating requests!
Alternatives:
# wrap the list in an iterator
requests = [dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk)]
responses = self.session_client.streaming_detect_intent(iter(requests))
# Note: The example in the source code calls the function like this
# but this gave me the same error
requests = [dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk)]
for response in self.session_client.streaming_detect_intent(requests):
# process response
Using generator structure
While this fixed the error, the intent detection still didn't work. I believe a better program structure is to use a generator, as suggested in the docs. Something like (pseudo-code):
def dialogflow_mic_stream_generator():
# open stream
audio_stream = ...
# send configuration request
query_input = dialogflow.types.QueryInput(audio_config=audio_config)
yield dialogflow.types.StreamingDetectIntentRequest(session=session_path,
query_input=query_input)
# output audio data from stream
while audio_stream_is_active:
chunk = audio_stream.read(chunk_size)
yield dialogflow.types.StreamingDetectIntentRequest(input_audio=chunk)
requests = dialogflow_mic_stream_generator()
responses = session_client.streaming_detect_intent(requests)
for response in responses:
# process response

Related

Why does Python-Requests return the same data when issuing POST in a loop even after closing sessions?

I'm using Python Requests but need to issue requests/POST to a SOAP API endpoint. However, I keep receiving the same response for multiple requests, even though I rebuild a new request in a loop. I've tried closing sessions/responses to no avail.
Manually issuing a request returns different data. What may I be missing?
Here's a sample of the code:
quoteVariables = """
<QuoteVariables>
{0}
</QuoteVariables>"""
for state, zipcode in states.iteritems():
for key, value in buckets.iteritems():
quotesXMLWithStateZip = buildQuote() #returns long xml string
quotes = quoteVariables.format(quotesXMLWithStateZip)
soapRequest=soapXML.format(state, quotes, value)
headers = {'content-Type':'text/xml;charset=UTF-8', 'SOAPAction':'http://my.url.com', 'Connection':'close'}
with requests.Session() as s:
response = s.post('https://my.url.com/endpoint', headers=headers, data=soapRequest, stream=False)
if response.status_code == 200:
xml = xmltodict.parse(response.text)
#fetch relevant part of xml resposne, ignore soap headers etc.,
else:
print "Failure! Status: "+response.status_code+" Reason: "+response.reason
response.close()
s.close()
#reset xml stuff to prevent stale data from lying around owing to string sharing/copying
quotesXMLWithStateZip = ""
quotes = ""
soapRequest = ""
Surprisingly moving this code into a separate class seems to solve the problem - my guess is something with globals/states seems to throw off the python-requests module (I could be wrong).
Also, moving to a class, the with block becomes optional i.e., whether it exists or not doesn't seem to matter - the code works as expected with/without it.
For example:
responses = {}
for state, zipcode in states.iteritems():
for key, value in buckets.iteritems():
fetcher = MyFetcher()
responses[state] = fetcher.getQuotes(state, zipcode, key, value)
print responses
The code of MyFetcher looks something like:
class MyFetcher:
def getQuotes(self, state, zipcode, key, value):
quotesXMLWithStateZip = self.buildQuote() #returns long xml string
quotes = self.quoteVariables.format(quotesXMLWithStateZip)
soapRequest=self.soapXML.format(state, quotes, value)
headers = {'content-Type':'text/xml;charset=UTF-8', 'SOAPAction':'http://my.url.com', 'Connection':'close'}
with requests.Session() as s: # <- ENTIRELY OPTIONAL
response = s.post('https://my.url.com/endpoint', headers=headers, data=soapRequest, stream=False)
if response.status_code == 200:
xml = xmltodict.parse(response.text)
#fetch relevant part of xml resposne, ignore soap headers etc.,
return parsedResponse
else:
print "Failure! Status: "+response.status_code+" Reason: "+response.reason
return None
response.close() # <- NOT NEEDED
s.close() # <- NOT NEEDED
I can only guess globals causing a problem somehow with the act of building/sending a request and/or receiving a response.

How to get multiple objects from S3 using boto3 get_object (Python 2.7)

I've got 100s of thousands of objects saved in S3. My requirement entails me needing to load a subset of these objects (anywhere between 5 to ~3000) and read the binary content of every object. From reading through the boto3/AWS CLI docs it looks like it's not possible to get multiple objects in one request so currently I have implemented this as a loop that constructs the key of every object, requests for the object then reads the body of the object:
for column_key in outstanding_column_keys:
try:
s3_object_key = "%s%s-%s" % (path_prefix, key, column_key)
data_object = self.s3_client.get_object(Bucket=bucket_key, Key=s3_object_key)
metadata_dict = data_object["Metadata"]
metadata_dict["key"] = column_key
metadata_dict["version"] = float(metadata_dict["version"])
metadata_dict["data"] = data_object["Body"].read()
records.append(Record(metadata_dict))
except Exception as exc:
logger.info(exc)
if len(records) < len(column_keys):
raise Exception("Some objects are missing!")
My issue is that when I attempt to get multiple objects (e.g 5 objects), I get back 3 and some aren't processed by the time I check if all objects have been loaded. I'm handling that in a custom exception. I'd come up with a solution to wrap the above code snippet in a while loop because I know the outstanding keys that I need:
while (len(outstanding_column_keys) > 0) and (load_attempts < 10):
for column_key in outstanding_column_keys:
try:
s3_object_key = "%s%s-%s" % (path_prefix, key, column_key)
data_object = self.s3_client.get_object(Bucket=bucket_key, Key=s3_object_key)
metadata_dict = data_object["Metadata"]
metadata_dict["key"] = column_key
metadata_dict["version"] = float(metadata_dict["version"])
metadata_dict["data"] = data_object["Body"].read()
records.append(Record(metadata_dict))
except Exception as exc:
logger.info(exc)
if len(records) < len(column_keys):
raise Exception("Some objects are missing!")
But I took this out suspecting that S3 is actually still processing the outstanding responses and the while loop would unnecessarily make additional requests for objects that S3 is already in the process of returning.
I did a separate investigation to verify that get_object requests are synchronous and it seems they are:
import boto3
import time
import os
s3_client = boto3.client('s3', aws_access_key_id=os.environ["S3_AWS_ACCESS_KEY_ID"], aws_secret_access_key=os.environ["S3_AWS_SECRET_ACCESS_KEY"])
print "Saving 3000 objects to S3..."
start = time.time()
for x in xrange(3000):
key = "greeting_{}".format(x)
s3_client.put_object(Body="HelloWorld!", Bucket='bucket_name', Key=key)
end = time.time()
print "Done saving 3000 objects to S3 in %s" % (end - start)
print "Sleeping for 20 seconds before trying to load the saved objects..."
time.sleep(20)
print "Loading the saved objects..."
arr = []
start_load = time.time()
for x in xrange(3000):
key = "greeting_{}".format(x)
try:
obj = s3_client.get_object(Bucket='bucket_name', Key=key)
arr.append(obj)
except Exception as exc:
print exc
end_load= time.time()
print "Done loading the saved objects. Found %s objects. Time taken - %s" % (len(arr), end_load - start_load)
My question and something I need confirmation is:
Whether the get_object requests are indeed synchronous? If they are then I expect that when I check for loaded objects in the first
code snippet then all of them should be returned.
If the get_object requests are asynchronous then how do I handle the responses in a way that avoids making extra requests to S3 for
objects that are still in the process of being returned?
Further clarity/refuting of any of my assumptions about S3 would also be appreciated.
Thank you!
Unlike Javascript, Python processes requests synchronously unless you do some sort of multithreading (which you aren't doing in your snippet above). In your for loop, you issue a request to s3_client.get_object, and that call blocks until the data is returned. Since the records array is smaller than it should be, that must mean that some exception is being thrown, and it should be caught in the except block:
except Exception as exc:
logger.info(exc)
If that isn't printing anything, it might be because logging is configured to ignore INFO level messages. If you aren't seeing any errors, you might try printing with logger.error.

Tests for Django Channels consumers: "No reply_channel sent to consumer"

I am using Django Channels with the #channel_session_user decorator (for access to Django's session data).
#channel_session_user_from_http
def ws_connect(message):
# creates group names like "group-1"
group_kw = get_group_id_for_user(message.user)
Group(group_kw).add(message.reply_channel)
#channel_session_user
def ws_receive(message):
group_kw = get_group_id_for_user(message.user)
payload = json.loads(message.content['text'])
Channel(payload['action']).send(message.content)
#channel_session_user
def ws_disconnect(message):
group_kw = get_group_id_for_user(message.user)
Group(group_kw).discard(message.reply_channel)
That works fine, but there is a problem when testing.
The below test should place a message on the websocket.receive channel, then ws_receive should take the message and place it on the channel defined in the message's action value. Finally, I test if it was in fact placed on that channel.
def test_send_chat_message_is_used_by_consumer(self):
# Make sure a user is authenticated
self.assertTrue(auth.get_user(self.client).is_authenticated())
payload = {'action': 'chat.receive',
'msg': 'Test message.',
'receiver': self.user2.id}
message = {'text': json.dumps(payload)}
# Send a chat message
Channel('websocket.receive').send(message)
# Receive it and place it on the right channel
ws_receive(self.get_next_message('websocket.receive', require=True))
# Fetch it from the channel
result = self.get_next_message(payload['action'], require=True)
# That should be the message sent
self.assertEqual(result, message)
Instead, I get the following error, pointing to the line with the ws_receive() call.
ValueError: No reply_channel sent to consumer; #channel_session can only be used on messages containing it.
The error is raised here in the Channels source.
Printing the reply_channel returns None instead of containing the correct reply channel name.
tmp = self.get_next_message('websocket.receive', require=True)
print(tmp.reply_channel) # prints: None
I am overlooking something obvious?
I think you cannot just simply call the consumer because it has the decorator #channel_session_user. You should try using the Client channels.tests provides.
from channels.tests import ChannelTestCase, Client
Then use something like this inside the test function
client = Client()
client.send('websocket.receive', content=message)
This is because the name that you provide to the 'Channel' object represents the channel this message was received on. Check the init function of Channel.
Also note the function get_next_message in tests/base.py file. The channel parameter here refers to (reply) channel to which the message was sent to during the test.
To answer your question -
Look at the docstring of Message object in channels/message.py. It says,
The message content is a dict called .content, while reply_channel is an optional extra attribute representing a channel to use to reply to this message's end user, if that makes sense.
You need to set the "reply_channel" in the dict that you send.
message = {
'text': json.dumps(payload),
'reply_channel': 'websocket.receive'
}
Hope it helps

Tweepy location on Twitter API filter always throws 406 error

I'm using the following code (from django management commands) to listen to the Twitter stream - I've used the same code on a seperate command to track keywords successfully - I've branched this out to use location, and (apparently rightly) wanted to test this out without disrupting my existing analysis that's running.
I've followed the docs and have made sure the box is in Long/Lat format (in fact, I'm using the example long/lat from the Twitter docs now). It looks broadly the same as the question here, and I tried using their version of the code from the answer - same error. If I switch back to using 'track=...', the same code works, so it's a problem with the location filter.
Adding a print debug inside streaming.py in tweepy so I can see what's happening, I print out the self.parameters self.url and self.headers from _run, and get:
{'track': 't,w,i,t,t,e,r', 'delimited': 'length', 'locations': '-121.7500,36.8000,-122.7500,37.8000'}
/1.1/statuses/filter.json?delimited=length and
{'Content-type': 'application/x-www-form-urlencoded'}
respectively - seems to me to be missing the search for location in some way shape or form. I don't believe I'm/I'm obviously not the only one using tweepy location search, so think it's more likely a problem in my use of it than a bug in tweepy (I'm on 2.3.0), but my implementation looks right afaict.
My stream handling code is here:
consumer_key = 'stuff'
consumer_secret = 'stuff'
access_token='stuff'
access_token_secret_var='stuff'
import tweepy
import json
# This is the listener, resposible for receiving data
class StdOutListener(tweepy.StreamListener):
def on_data(self, data):
# Twitter returns data in JSON format - we need to decode it first
decoded = json.loads(data)
#print type(decoded), decoded
# Also, we convert UTF-8 to ASCII ignoring all bad characters sent by users
try:
user, created = read_user(decoded)
print "DEBUG USER", user, created
if decoded['lang'] == 'en':
tweet, created = read_tweet(decoded, user)
print "DEBUG TWEET", tweet, created
else:
pass
except KeyError,e:
print "Error on Key", e
pass
except DataError, e:
print "DataError", e
pass
#print user, created
print ''
return True
def on_error(self, status):
print status
l = StdOutListener()
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret_var)
stream = tweepy.Stream(auth, l)
#locations must be long, lat
stream.filter(locations=[-121.75,36.8,-122.75,37.8], track='twitter')
The issue here was the order of the coordinates.
Correct format is:
SouthWest Corner(Long, Lat), NorthEast Corner(Long, Lat). I had them transposed. :(
The streaming API doesn't allow to filter by location AND keyword simultaneously.
you must refer to this answer i had the same problem earlier
https://stackoverflow.com/a/22889470/4432830

AT commands with pyserial not working with receiving sms

This is a code snippet written in python to receive sms via a usb modem. When I run the program all I get is a status message "OK"., but nothing else.How do I fix the issue to print the messages I am receiving?
import serial
class HuaweiModem(object):
def __init__(self):
self.open()
def open(self):
self.ser = serial.Serial('/dev/ttyUSB_utps_modem', 115200, timeout=1)
self.SendCommand('ATZ\r')
self.SendCommand('AT+CMGF=1\r')
def SendCommand(self,command, getline=True):
self.ser.write(command)
data = ''
if getline:
data=self.ReadLine()
return data
def ReadLine(self):
data = self.ser.readline()
print data
return data
def GetAllSMS(self):
self.ser.flushInput()
self.ser.flushOutput()
command = 'AT+CMGL="all"\r'
print self.SendCommand(command,getline=False)
self.ser.timeout = 2
data = self.ser.readline()
print data
while data !='':
data = self.ser.readline()
if data.find('+cmgl')>0:
print data
h = HuaweiModem()
h.GetAllSMS()
In GetAllSMS there are two things I notice:
1) You are using self.ser.readline and not self.Readline so GetAllSMS will not try to print anything (except the first response line) before the OK final response is received, and at that point data.find('+cmgl')>0 will never match.
Is that just the problem?
2) Will print self.SendCommand(command,getline=False) call the function just as it were written as self.SendCommand(command,getline=False)? (Just checking since I do not write python myself)
In any case, you should rework your AT parsing a bit.
def SendCommand(self,command, getline=True):
The getline parameter here is not a very good abstraction. Leave out reading responses from the SendCommand function. You should rather implement proper parsing of the responses given back by the modem and handle that outside. In the general case something like
self.SendCommand('AT+CSOMECMD\r')
data = self.ser.readline()
while ! IsFinalResult(data):
data = self.ser.readline()
print data # or do whatever you want with each line
For commands without any explicit processing of the responses, you can implement a SendCommandAndWaitForFinalResponse function that does the above.
See this answer for more information about a IsFinalResult function.
where you are having problems is here in your GetAllSMS function. Now replace my GeTALLSMS function with yours and see what happens
def GetAllSMS(self):
self.ser.flushInput()
self.ser.flushOutput()
command = 'AT+CMGL="all"\r' #to get all messages both read and unread
print self.SendCommand(command,getline=False)
while 1:
self.ser.timeout = 2
data = self.ser.readline()
print data
or this
def GetAllSMS(self):
self.ser.flushInput()
self.ser.flushOutput()
command = 'AT+CMGL="all"\r' #to get all messages both read and unread
print self.SendCommand(command,getline=False)
self.ser.timeout = 2
data = self.ser.readall() #you can also u read(10000000)
print data
thats all...