dialogflow_client.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Dialogflow
4 import dialogflow_v2beta1
5 from dialogflow_v2beta1.types import Context, EventInput, InputAudioConfig, \
6  OutputAudioConfig, QueryInput, QueryParameters, \
7  StreamingDetectIntentRequest, TextInput
8 from dialogflow_v2beta1.gapic.enums import AudioEncoding, OutputAudioEncoding
9 import google.api_core.exceptions
10 import utils
11 from AudioServerStream import AudioServerStream
12 from MicrophoneStream import MicrophoneStream
13 
14 # Python
15 import pyaudio
16 import signal
17 
18 import time
19 from uuid import uuid4
20 from yaml import load, YAMLError
21 # ROS
22 import rospy
23 import rospkg
24 from std_msgs.msg import String
25 from dialogflow_ros.msg import *
26 
27 # Use to convert Struct messages to JSON
28 # from google.protobuf.json_format import MessageToJson
29 
30 
31 class DialogflowClient(object):
32  def __init__(self, language_code='en-US', last_contexts=None):
33  """Initialize all params and load data"""
34  """ Constants and params """
35  self.CHUNK = 4096
36  self.FORMAT = pyaudio.paInt16
37  self.CHANNELS = 1
38  self.RATE = 16000
39  self.USE_AUDIO_SERVER = rospy.get_param('/dialogflow_client/use_audio_server', False)
40  self.PLAY_AUDIO = rospy.get_param('/dialogflow_client/play_audio', True)
41  self.DEBUG = rospy.get_param('/dialogflow_client/debug', False)
42 
43  # Register Ctrl-C sigint
44  signal.signal(signal.SIGINT, self._signal_handler)
45 
46  """ Dialogflow setup """
47  # Get hints/clues
48  rp = rospkg.RosPack()
49  file_dir = rp.get_path('dialogflow_ros') + '/config/context.yaml'
50  with open(file_dir, 'r') as f:
51  try:
52  self.phrase_hints = load(f)
53  except YAMLError:
54  rospy.logwarn("DF_CLIENT: Unable to open phrase hints yaml file!")
55  self.phrase_hints = []
56 
57  # Dialogflow params
58  project_id = rospy.get_param('/dialogflow_client/project_id', 'my-project-id')
59  session_id = str(uuid4()) # Random
60  self._language_code = language_code
61  self.last_contexts = last_contexts if last_contexts else []
62  # DF Audio Setup
63  audio_encoding = AudioEncoding.AUDIO_ENCODING_LINEAR_16
64  # Possibel models: video, phone_call, command_and_search, default
65  self._audio_config = InputAudioConfig(audio_encoding=audio_encoding,
66  language_code=self._language_code,
67  sample_rate_hertz=self.RATE,
68  phrase_hints=self.phrase_hints,
69  model='command_and_search')
70  self._output_audio_config = OutputAudioConfig(
71  audio_encoding=OutputAudioEncoding.OUTPUT_AUDIO_ENCODING_LINEAR_16
72  )
73  # Create a session
74  self._session_cli = dialogflow_v2beta1.SessionsClient()
75  self._session = self._session_cli.session_path(project_id, session_id)
76  rospy.logdebug("DF_CLIENT: Session Path: {}".format(self._session))
77 
78  """ ROS Setup """
79  results_topic = rospy.get_param('/dialogflow_client/results_topic',
80  '/dialogflow_client/results')
81  requests_topic = rospy.get_param('/dialogflow_client/requests_topic',
82  '/dialogflow_client/requests')
83  text_req_topic = requests_topic + '/string_msg'
84  text_event_topic = requests_topic + '/string_event'
85  msg_req_topic = requests_topic + '/df_msg'
86  event_req_topic = requests_topic + '/df_event'
87  self._results_pub = rospy.Publisher(results_topic, DialogflowResult,
88  queue_size=10)
89  rospy.Subscriber(text_req_topic, String, self._text_request_cb)
90  rospy.Subscriber(text_event_topic, String, self._text_event_cb)
91  rospy.Subscriber(msg_req_topic, DialogflowRequest, self._msg_request_cb)
92  rospy.Subscriber(event_req_topic, DialogflowEvent, self._event_request_cb)
93 
94  """ Audio setup """
95  # Mic stream input setup
96  self.audio = pyaudio.PyAudio()
97  self._server_name = rospy.get_param('/dialogflow_client/server_name',
98  '127.0.0.1')
99  self._port = rospy.get_param('/dialogflow_client/port', 4444)
100 
101  if self.PLAY_AUDIO:
102  self._create_audio_output()
103 
104  rospy.logdebug("DF_CLIENT: Last Contexts: {}".format(self.last_contexts))
105  rospy.loginfo("DF_CLIENT: Ready!")
106 
107  # ========================================= #
108  # ROS Utility Functions #
109  # ========================================= #
110 
111  def _text_request_cb(self, msg):
112  """ROS Callback that sends text received from a topic to Dialogflow,
113  :param msg: A String message.
114  :type msg: String
115  """
116  rospy.logdebug("DF_CLIENT: Request received")
117  new_msg = DialogflowRequest(query_text=msg.data)
118  df_msg = self.detect_intent_text(new_msg)
119 
120  def _msg_request_cb(self, msg):
121  """ROS Callback that sends text received from a topic to Dialogflow,
122  :param msg: A DialogflowRequest message.
123  :type msg: DialogflowRequest
124  """
125  df_msg = self.detect_intent_text(msg)
126  rospy.logdebug("DF_CLIENT: Request received:\n{}".format(df_msg))
127 
128  def _event_request_cb(self, msg):
129  """
130  :param msg: DialogflowEvent Message
131  :type msg: DialogflowEvent"""
132  new_event = utils.converters.events_msg_to_struct(msg)
133  self.event_intent(new_event)
134 
135  def _text_event_cb(self, msg):
136  new_event = EventInput(name=msg.data, language_code=self._language_code)
137  self.event_intent(new_event)
138 
139  # ================================== #
140  # Setters/Getters #
141  # ================================== #
142 
143  def get_language_code(self):
144  return self._language_code
145 
146  def set_language_code(self, language_code):
147  assert isinstance(language_code, str), "Language code must be a string!"
148  self._language_code = language_code
149 
150  # ==================================== #
151  # Utility Functions #
152  # ==================================== #
153 
154  def _signal_handler(self, signal, frame):
155  rospy.logwarn("\nDF_CLIENT: SIGINT caught!")
156  self.exit()
157 
158  # ----------------- #
159  # Audio Utilities #
160  # ----------------- #
161 
163  """Creates a PyAudio output stream."""
164  rospy.logdebug("DF_CLIENT: Creating audio output...")
165  self.stream_out = self.audio.open(format=pyaudio.paInt16,
166  channels=1,
167  rate=24000,
168  output=True)
169 
170  def _play_stream(self, data):
171  """Simple function to play a the output Dialogflow response.
172  :param data: Audio in bytes.
173  """
174  self.stream_out.start_stream()
175  self.stream_out.write(data)
176  time.sleep(0.2) # Wait for stream to finish
177  self.stream_out.stop_stream()
178 
179  # -------------- #
180  # DF Utilities #
181  # -------------- #
182 
183  def _generator(self):
184  """Generator function that continuously yields audio chunks from the
185  buffer. Used to stream data to the Google Speech API Asynchronously.
186  :return A streaming request with the audio data.
187  First request carries config data per Dialogflow docs.
188  :rtype: Iterator[:class:`StreamingDetectIntentRequest`]
189  """
190  # First message contains session, query_input, and params
191  query_input = QueryInput(audio_config=self._audio_config)
192  contexts = utils.converters.contexts_msg_to_struct(self.last_contexts)
193  params = QueryParameters(contexts=contexts)
194  req = StreamingDetectIntentRequest(
195  session=self._session,
196  query_input=query_input,
197  query_params=params,
198  single_utterance=True,
199  output_audio_config=self._output_audio_config
200  )
201  yield req
202 
203  if self.USE_AUDIO_SERVER:
204  with AudioServerStream() as stream:
205  audio_generator = stream.generator()
206  for content in audio_generator:
207  yield StreamingDetectIntentRequest(input_audio=content)
208  else:
209  with MicrophoneStream() as stream:
210  audio_generator = stream.generator()
211  for content in audio_generator:
212  yield StreamingDetectIntentRequest(input_audio=content)
213 
214  # ======================================== #
215  # Dialogflow Functions #
216  # ======================================== #
217 
218  def detect_intent_text(self, msg):
219  """Use the Dialogflow API to detect a user's intent. Goto the Dialogflow
220  console to define intents and params.
221  :param msg: DialogflowRequest msg
222  :return query_result: Dialogflow's query_result with action parameters
223  :rtype: DialogflowResult
224  """
225  # Create the Query Input
226  text_input = TextInput(text=msg.query_text, language_code=self._language_code)
227  query_input = QueryInput(text=text_input)
228  # Create QueryParameters
229  user_contexts = utils.converters.contexts_msg_to_struct(msg.contexts)
230  self.last_contexts = utils.converters.contexts_msg_to_struct(self.last_contexts)
231  contexts = self.last_contexts + user_contexts
232  params = QueryParameters(contexts=contexts)
233  try:
234  response = self._session_cli.detect_intent(
235  session=self._session,
236  query_input=query_input,
237  query_params=params,
238  output_audio_config=self._output_audio_config
239  )
240  except google.api_core.exceptions.ServiceUnavailable:
241  rospy.logwarn("DF_CLIENT: Deadline exceeded exception caught. The response "
242  "took too long or you aren't connected to the internet!")
243  else:
244  # Store context for future use
245  self.last_contexts = utils.converters.contexts_struct_to_msg(
246  response.query_result.output_contexts
247  )
248  df_msg = utils.converters.result_struct_to_msg(
249  response.query_result)
250  rospy.loginfo(utils.output.print_result(response.query_result))
251  # Play audio
252  if self.PLAY_AUDIO:
253  self._play_stream(response.output_audio)
254  self._results_pub.publish(df_msg)
255  return df_msg
256 
257  def detect_intent_stream(self, return_result=False):
258  """Gets data from an audio generator (mic) and streams it to Dialogflow.
259  We use a stream for VAD and single utterance detection."""
260 
261  # Generator yields audio chunks.
262  requests = self._generator()
263  responses = self._session_cli.streaming_detect_intent(requests)
264  resp_list = []
265  try:
266  for response in responses:
267  resp_list.append(response)
268  rospy.logdebug(
269  'DF_CLIENT: Intermediate transcript: "{}".'.format(
270  response.recognition_result.transcript))
271  except google.api_core.exceptions.Cancelled as c:
272  rospy.logwarn("DF_CLIENT: Caught a Google API Client cancelled "
273  "exception. Check request format!:\n{}".format(c))
274  except google.api_core.exceptions.Unknown as u:
275  rospy.logwarn("DF_CLIENT: Unknown Exception Caught:\n{}".format(u))
276  except google.api_core.exceptions.ServiceUnavailable:
277  rospy.logwarn("DF_CLIENT: Deadline exceeded exception caught. The response "
278  "took too long or you aren't connected to the internet!")
279  else:
280  if response is None:
281  rospy.logwarn("DF_CLIENT: No response received!")
282  return None
283  # The response list returns responses in the following order:
284  # 1. All intermediate recognition results
285  # 2. The Final query recognition result (no audio!)
286  # 3. The output audio with config
287  final_result = resp_list[-2].query_result
288  final_audio = resp_list[-1]
289  self.last_contexts = utils.converters.contexts_struct_to_msg(
290  final_result.output_contexts
291  )
292  df_msg = utils.converters.result_struct_to_msg(final_result)
293  rospy.loginfo(utils.output.print_result(final_result))
294  # Play audio
295  if self.PLAY_AUDIO:
296  self._play_stream(final_audio.output_audio)
297  # Pub
298  self._results_pub.publish(df_msg)
299  if return_result: return df_msg, final_result
300  return df_msg
301 
302  def event_intent(self, event):
303  """Send an event message to Dialogflow
304  :param event: The ROS event message
305  :type event: DialogflowEvent
306  :return: The result from dialogflow as a ROS msg
307  :rtype: DialogflowResult
308  """
309  # Convert if needed
310  if type(event) is DialogflowEvent:
311  event_input = utils.converters.events_msg_to_struct(event)
312  else:
313  event_input = event
314 
315  query_input = QueryInput(event=event_input)
316  params = utils.converters.create_query_parameters(
317  contexts=self.last_contexts
318  )
319  response = self._session_cli.detect_intent(
320  session=self._session,
321  query_input=query_input,
322  query_params=params,
323  output_audio_config=self._output_audio_config
324  )
325  df_msg = utils.converters.result_struct_to_msg(response.query_result)
326  if self.PLAY_AUDIO:
327  self._play_stream(response.output_audio)
328  return df_msg
329 
330  def start(self):
331  """Start the dialogflow client"""
332  rospy.loginfo("DF_CLIENT: Spinning...")
333  rospy.spin()
334 
335  def exit(self):
336  """Close as cleanly as possible"""
337  rospy.loginfo("DF_CLIENT: Shutting down")
338  self.audio.terminate()
339  exit()
340 
341 
342 if __name__ == '__main__':
343  rospy.init_node('dialogflow_client')
345  df.start()
346  # df.detect_intent_stream()
def __init__(self, language_code='en-US', last_contexts=None)
def detect_intent_stream(self, return_result=False)


dialogflow_ros
Author(s): Anas Abou Allaban
autogenerated on Mon Jun 10 2019 13:02:59