def on_response(self, msg): """ 応答の受信 :param dict msg: dictionary converted from json str topic : raspberrypi/response int qos : json payload : {"param1": "...", "param2": "..."} payload : {"action": "...", "request_id": "..."} :str action: requestで指定したaction :str request_id: requestで指定したrequest_id """ try: self.logger.info("Topic: " + str(msg.topic)) self.logger.info("QoS: " + str(msg.qos)) self.logger.info("Payload: " + str(msg.payload)) # topic 確認 # Level1:既定文字列のチェック levels_pub = msg.topic.split('/', 2) levels_sub = self.topic_sub.split('/') if levels_pub[0] != levels_sub[0]: raise ParamError("invalid topic.") # Level2:typeのチェック if levels_pub[1] != levels_sub[1]: raise ParamError("invalid type.") # responseをjsonデコード response = json.loads(msg.payload) # リクエストIDのチェック if not response['request_id']: raise ParamError("can't find request_id.") if response['request_id'] != self.request_id: raise ParamError("invalid request_id.") # 処理終了 self.response = response self.isReceived = True self.logger.info('received valid response.') except Error as e: self.logger.error(e.description) # 正しい応答を待つ except Exception as e: self.logger.critical(e) self.logger.critical(util.trace())
def lambda_handler(event, context): """ Lambda Handler :param str event['sender']: (required) 'slack' or 'raspberrypi' :param str event['action']: (required) RaspberryPiに指示するコマンド :param str event['user']: (optional) 指示ユーザ sender: slack -- action: speak -- :param str event['text'] : (required) 再生するメッセージ raspberrypi -- action: listend -- :param str event['result'] : (required) 音声聞き取りの結果 """ try: # ルートハンドラ除去&ロギング設定 _lambda.util.basicConfig() logger = logging.getLogger(__file__) logger.info(event) # API Gateway(Slack) or Aws IoT(RaspberryPi) if 'sender' not in event: raise ParamError("senderが指定されていません。") else: sender = event['sender'] except Exception as exp: logger.critical(exp) logger.critical(util.trace()) raise exp try: # 呼び出し元によって分岐 if sender == 'raspberrypi': # パラメータを取得 if 'action' not in event: raise ParamError("actionが指定されていません。") action = event['action'] # actionに従った処理 if action == 'listened': # パラメータ準備 if 'result' not in event: raise ParamError("resultが指定されていません。") text = event['result'] # Lambda設定の確認 if 'SLACK_WEBHOOK_URL' not in os.environ: raise InternalError("SLACK_WEBHOOK_URLが設定されていません。") # Slackへ通知 slack = IncomingWebHooks( url = os.environ['SLACK_WEBHOOK_URL'], logging = logging ) slack.webhook(text) else: raise ParamError("定義されていないactionです。") return 'success' else: # パラメータを取得 if 'action' not in event: raise HttpParamError("actionが指定されていません。") action = event['action'] user = None if 'user' in event: user = event['user'] # actionに従った処理 if action == 'speak': # パラメータ準備 if 'text' not in event: raise HttpParamError("textが指定されていません。") text = event['text'] # テキスト処理 ## リマインダ用のprefixを削除 text = re.sub(r"^Reminder: ", "", text) # 音声出力処理 cwd = os.getcwd() pi = RaspberryPi( host = os.environ['SUBSCRIBE_HOST'] if 'SUBSCRIBE_HOST' in os.environ else None, ca = cwd + '/' + os.environ['SUBSCRIBE_CAROOTFILE'] if 'SUBSCRIBE_CAROOTFILE' in os.environ else None, cert = cwd + '/' + os.environ['SUBSCRIBE_CERTFILE'] if 'SUBSCRIBE_CERTFILE' in os.environ else None, key = cwd + '/' + os.environ['SUBSCRIBE_KEYFILE'] if 'SUBSCRIBE_KEYFILE' in os.environ else None, logging = logging ) result = pi.speak(text) logger.info(result) else: raise HttpParamError("定義されていないactionです。") # 正常終了 return apigateway.success(result + "invoked %s" % (user)) except Error as err: logger.error(err) e = err except Exception as exp: logger.critical(exp) logger.critical(util.trace()) e = HttpInternalError("内部エラーが発生しました。") # 共通エラー処理(呼び元に従う) if sender == 'raspberrypi': # 通常のエラー to AWS IoT / InvocationType : Event raise e else: # 200としてエラー to API Gateway /InvocationType : RequestResponse return apigateway.error(e.statusCode, e.error, e.description)
def run(is_daemon = False): AwsIoTSpeaker( file_config = 'config.ini', is_daemon = is_daemon ).run() def daemonize(): pid = os.fork() if pid > 0: f = open('/var/run/aws-iot-speakerd.pid', 'w') f.write(str(pid) + "\n") f.close() sys.exit() elif pid == 0: run(True) if __name__== '__main__': try: # コマンドライン引数を判定 if '-D' in sys.argv: # デーモン起動 daemonize() else: # 通常起動 run(False) except Exception as e: print(e) print(util.trace())
def _on_message(self, mosq, obj, msg): """ :param dict msg: dictionary converted from json str topic : raspberrypi/request/{action} int qos : json payload : {"param1": "...", "param2": "..."} action : speak -- payload : {"text": "...", "voice": "..."} :str text: 再生するテキスト :str voice: "Takumi" or "Mizuki" """ try: self.logger.info("Topic: " + str(msg.topic)) self.logger.info("QoS: " + str(msg.qos)) self.logger.info("Payload: " + str(msg.payload)) ack = {} # topic 確認 # Level1:既定文字列のチェック levels_pub = msg.topic.split('/', 2) levels_sub = self.topic_sub.split('/') if levels_pub[0] != levels_sub[0]: raise ParamError("invalid topic.") # Level2:typeのチェック if levels_pub[1] != levels_sub[1]: raise ParamError("invalid type.") # Level3:actionのチェックと取得 if len(levels_pub) < 3 : raise ParamError("can't find action.") action = levels_pub[2] # レスポンス ack['action'] = action """ # 一応下位を取得している subtopics = None if len(topics_pub) > 2 : subtopics = topics_pub[2].split('/') """ # パラメータをjsonデコード param = json.loads(msg.payload) # リクエストIDをチェック、ACKに設定 if not param['request_id']: raise ParamError("can't find request_id.") ack['request_id'] = param['request_id'] # action毎の処理 if action == 'speak': self.speaker.play(param) ack['result'] = '指定されたテキストを読み上げました。' else: raise ParamError("can't find action.") # 処理終了 self.logger.info('success') except ParamError as e: # 他のメッセージを処理しない self.logger.info(e.description) return except Error as e: self.logger.error(e.description) ack['error'] = e.error ack['error_description'] = e.description except Exception as e: self.logger.critical(e) self.logger.critical(util.trace()) ack['error'] = 'internal_error' ack['error_description'] = str(e) # ACK返却 self.publish(self.topic_pub, **ack) self.logger.info('on message complete.')
def _on_message(self, mosq, obj, msg): """ :param dict msg: dictionary converted from json str topic : raspberrypi/request/{action} int qos : json payload : {"param1": "...", "param2": "..."} action : listen -- payload : null """ try: self.logger.info("Topic: " + str(msg.topic)) self.logger.info("QoS: " + str(msg.qos)) self.logger.info("Payload: " + str(msg.payload)) ack = {} ack['sender'] = 'raspberrypi' # topic 確認 # Level1:既定文字列のチェック levels_pub = msg.topic.split('/', 2) levels_sub = self.topic_sub.split('/') if levels_pub[0] != levels_sub[0]: raise ParamError("invalid topic.") # Level2:typeのチェック if levels_pub[1] != levels_sub[1]: raise ParamError("invalid type.") # Level3:actionのチェックと取得 if len(levels_pub) < 3: raise ParamError("can't find action.") action = levels_pub[2] # action毎の処理 if action == 'listen': ack['action'] = 'listened' # 音声録音とテキスト変換 result = self.listener.listen() ack['result'] = result else: raise ParamError("can't find action.") # 処理終了 self.logger.info('on message success.') except ParamError as e: # 他のメッセージを処理しない self.logger.info(e.description) return except RecognitionError as e: # 通常変換失敗時 self.logger.error(e.description) ack['result'] = "音声の聞き取りに失敗しました。" except Error as e: # その他ユーザエラー全般 self.logger.error(e.description) ack['result'] = "録音処理中にエラーが発生しました。" except Exception as e: # その他エラー全般 self.logger.critical(e) self.logger.critical(util.trace()) ack['result'] = "録音処理中にエラーが発生しました。" # 応答返却 self.publish(self.topic_pub, **ack) self.logger.info('on message complete.')
def _record(self): chunk = 1024 * 20 #サンプリングレート、マイク性能に依存 RATE = 44100 #録音時間 RECORD_SECONDS = 10 #マイク番号を設定: cat /proc/asound/modules input_device_index = self.input_device_index #マイクからデータ取得 stream = self.audio.open(format=pyaudio.paInt16, channels=1, rate=RATE, input=True, frames_per_buffer=chunk) try: # 録音処理 sec_per_buffer = RATE / chunk SEC_STOP = 1.5 threshold_start = 0.002 # 何か音が有ったら開始 threshold_end = 0.002 # 音がある程度無くなったら終了 no_sound = 0 recording = False all = [] self.logger.info("recording...") for i in range(0, sec_per_buffer * RECORD_SECONDS): data = stream.read(chunk, False) # @refs https://qiita.com/mix_dvd/items/dc53926b83a9529876f7 # 最大1に正規化 x = np.frombuffer(data, dtype="int16") / 32768.0 # 正負に伸びる、バラつきあるので平均化 ave = np.average(np.absolute(x)) # self.logger.info(str(i)) # self.logger.info("x(cnt): " + str(len(x))) self.logger.info("x(ave): " + str(ave)) # self.logger.info("x(max): " + str(x.max())) # self.logger.info("x(min): " + str(x.min())) if not recording: if ave > threshold_start: recording = True else: # 録音データを取得 all.append(data) # 無音が暫く続いたら終了 if ave < threshold_end: no_sound += 1 if no_sound >= (sec_per_buffer * SEC_STOP): break else: no_sound = 0 stream.close() self.logger.info("recorded.") except Exception as e: stream.close() self.logger.critical(e) self.logger.critical(util.trace()) raise e ## ファイルが存在した場合は削除 if os.path.isfile(self.path_voice): os.remove(self.path_voice) # 書き込み out = wave.open(self.path_voice, 'w') try: out.setnchannels(1) #mono out.setsampwidth(2) #16bits out.setframerate(RATE) out.writeframes(''.join(all)) out.close() except Exception as e: out.close() self.logger.critical(e) self.logger.critical(util.trace()) raise e