/* * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * Copyright 2019 Konsulko Group * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ #include "VshlCoreApi.h" #include #include #include #include #include #include #include "afb/AFBApiImpl.h" #include "afb/AFBRequestImpl.h" #include "core/VRRequestProcessor.h" #include "utilities/events/EventRouter.h" #include "utilities/logging/Logger.h" #include "voiceagents/VoiceAgentEventNames.h" #include "voiceagents/VoiceAgentsDataManager.h" using namespace std; CTLP_CAPI_REGISTER("vshl-core-api"); static std::string TAG = "vshlcore::plugins::VshlCoreAPI"; static std::string VA_JSON_ATTR_DEFAULT = "default"; static std::string VA_JSON_ATTR_AGENTS = "agents"; static std::string VA_JSON_ATTR_ID = "id"; static std::string VA_JSON_ATTR_NAME = "name"; static std::string VA_JSON_ATTR_API = "api"; static std::string VA_JSON_ATTR_ACTIVE = "active"; static std::string VA_JSON_ATTR_WWS = "wakewords"; static std::string VA_JSON_ATTR_ACTIVE_WW = "activewakeword"; static std::string VA_JSON_ATTR_DESCRIPTION = "description"; static std::string VA_JSON_ATTR_VENDOR = "vendor"; static std::string STARTLISTENING_JSON_ATTR_REQUEST = "request_id"; static std::string SUBSCRIBE2LOGINEVENTS_JSON_ATTR_REQUEST = "request_id"; static std::string EVENTS_JSON_ATTR_VA_ID = "va_id"; static std::string EVENTS_JSON_ATTR_EVENTS = "events"; static std::string CONFIG_JSON_FILE = "/etc/xdg/AGL/voice-high.json"; static std::string JSON_EXT = ".json"; static std::string VA_CONFIG_JSON_DIR = "/etc/xdg/AGL/voiceagents"; static std::shared_ptr sLogger; static std::shared_ptr sAfbApi; static std::unique_ptr sVRRequestProcessor; static std::unique_ptr sVoiceAgentsDataManager; static std::unique_ptr sEventRouter; using json = nlohmann::json; using Level = vshlcore::utilities::logging::Logger::Level; CTLP_ONLOAD(plugin, ret) { if (plugin->api == nullptr) { return -1; } // Logger sLogger = vshlcore::utilities::logging::Logger::create(plugin->api); // sLogger->log(Level::INFO, TAG, "Vshl plugin loaded & initialized."); // AFB Wrapper sAfbApi = vshlcore::afb::AFBApiImpl::create(plugin->api); // VRRequestProcessor auto vrRequestProcessorDelegate = vshlcore::core::VRRequestProcessorDelegate::create(sLogger, sAfbApi); sVRRequestProcessor = vshlcore::core::VRRequestProcessor::create(sLogger, vrRequestProcessorDelegate); if (!sVRRequestProcessor) { sLogger->log(Level::ERROR, TAG, "Failed to create VRRequestProcessor"); return -1; } // VoiceAgentDataManager sVoiceAgentsDataManager = vshlcore::voiceagents::VoiceAgentsDataManager::create(sLogger, sAfbApi); if (!sVoiceAgentsDataManager) { sLogger->log(Level::ERROR, TAG, "Failed to create VoiceAgentsDataManager"); return -1; } sVoiceAgentsDataManager->addVoiceAgentsChangeObserver(sVRRequestProcessor->getVoiceAgentsChangeObserver()); // EventRouter sEventRouter = vshlcore::utilities::events::EventRouter::create(sLogger); if (!sEventRouter) { sLogger->log(Level::ERROR, TAG, "Failed to create EventRouter"); return -1; } sEventRouter->addEventFilter(sVoiceAgentsDataManager->getEventFilter()); return 0; } CTLP_CAPI(onAuthStateEvent, source, argsJ, eventJ) { if (sEventRouter == nullptr) { return -1; } string eventName = vshlcore::voiceagents::VSHL_EVENT_AUTH_STATE_EVENT; json eventJson = json::parse(json_object_to_json_string(eventJ)); if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { sLogger->log(Level::ERROR, TAG, "onAuthStateEvent: No voiceagent id found."); return -1; } std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); return 0; } CTLP_CAPI(onConnectionStateEvent, source, argsJ, eventJ) { if (sEventRouter == nullptr) { return -1; } string eventName = vshlcore::voiceagents::VSHL_EVENT_CONNECTION_STATE_EVENT; json eventJson = json::parse(json_object_to_json_string(eventJ)); if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { sLogger->log(Level::ERROR, TAG, "onConnectionStateEvent: No voiceagent id found."); return -1; } std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); return 0; } CTLP_CAPI(onDialogStateEvent, source, argsJ, eventJ) { if (sEventRouter == nullptr) { return -1; } string eventName = vshlcore::voiceagents::VSHL_EVENT_DIALOG_STATE_EVENT; json eventJson = json::parse(json_object_to_json_string(eventJ)); if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { sLogger->log(Level::ERROR, TAG, "onDialogStateEvent: No voiceagent id found."); return -1; } std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); return 0; } CTLP_CAPI(onLoginPairReceivedEvent, source, argsJ, eventJ) { if (sEventRouter == nullptr) { return -1; } string eventName = vshlcore::voiceagents::VSHL_EVENT_LOGINPAIR_RECEIVED_EVENT; json eventJson = json::parse(json_object_to_json_string(eventJ)); if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { sLogger->log(Level::ERROR, TAG, "onLoginPairReceivedEvent: No voiceagent id found."); return -1; } std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); return 0; } CTLP_CAPI(onLoginPairExpiredEvent, source, argsJ, eventJ) { if (sEventRouter == nullptr) { return -1; } string eventName = vshlcore::voiceagents::VSHL_EVENT_LOGINPAIR_EXPIRED_EVENT; json eventJson = json::parse(json_object_to_json_string(eventJ)); if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) { sLogger->log(Level::ERROR, TAG, "onLoginPairExpiredEvent: No voiceagent id found."); return -1; } std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get()); sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ)); return 0; } // Helper function to add events for voice agent to controller // configuration. It relies on the controller configuration being // available via the userdata of the API pointer. static int AddVoiceAgentEvents(std::string &agentApi) { // Retrieve section config from api handle CtlConfigT *ctrlConfig = (CtlConfigT*) afb_api_get_userdata(sAfbApi->getApi()); if (ctrlConfig == nullptr) { sLogger->log(Level::ERROR, TAG, "AddVoiceAgentEvents: ctrlConfig == nullptr"); return -1; } CtlSectionT* section = nullptr; for (int idx = 0; ctrlConfig->sections[idx].key != NULL; ++idx) { if (!strcasecmp(ctrlConfig->sections[idx].key, "events")) { section = &(ctrlConfig->sections[idx]); break; } } if (section == nullptr) { sLogger->log(Level::ERROR, TAG, "AddVoiceAgentEvents: events section not found"); return -1; } // Fill out events JSON array to pass to the controller json_object* eventsJ = json_object_new_array(); json_object *eventJ = json_object_new_object(); std::string uid = agentApi + "/voice_authstate_event"; json_object *uidJ = json_object_new_string(uid.c_str()); json_object_object_add(eventJ, "uid", uidJ); json_object *actionJ = json_object_new_string("plugin://vshl-core#onAuthStateEvent"); json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); eventJ = json_object_new_object(); uid = agentApi + "/voice_connectionstate_event"; uidJ = json_object_new_string(uid.c_str()); json_object_object_add(eventJ, "uid", uidJ); actionJ = json_object_new_string("plugin://vshl-core#onConnectionStateEvent"); json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); eventJ = json_object_new_object(); uid = agentApi + "/voice_dialogstate_event"; uidJ = json_object_new_string(uid.c_str()); json_object_object_add(eventJ, "uid", uidJ); actionJ = json_object_new_string("plugin://vshl-core#onDialogStateEvent"); json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); eventJ = json_object_new_object(); uid = agentApi + "/voice_cbl_codepair_received_event"; uidJ = json_object_new_string(uid.c_str()); json_object_object_add(eventJ, "uid", uidJ); actionJ = json_object_new_string("plugin://vshl-core#onLoginPairReceivedEvent"); json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); eventJ = json_object_new_object(); uid = agentApi + "/voice_cbl_codepair_expired_event"; uidJ = json_object_new_string(uid.c_str()); json_object_object_add(eventJ, "uid", uidJ); actionJ = json_object_new_string("plugin://vshl-core#onLoginPairExpiredEvent"); json_object_object_add(eventJ, "action", actionJ); json_object_array_add(eventsJ, eventJ); // Call into controller to add event actions // NOTE: AFAICT the JSON objects end up reused by the controller data // structures, so eventsJ should not be freed with a put after // this call. int rc = AddActionsToSection(sAfbApi->getApi(), section, eventsJ, 0); if (rc != 0) { stringstream message; message << "AddVoiceAgentEvents: AddActionsToSection rc = " << rc; sLogger->log(Level::WARNING, TAG, message.str().c_str()); } return rc; } // Helper function to parse a voiceagent's configuration static bool parseVoiceAgentConfig(const json &agentJson) { if (agentJson.find(VA_JSON_ATTR_ID) == agentJson.end() || agentJson.find(VA_JSON_ATTR_ACTIVE) == agentJson.end() || agentJson.find(VA_JSON_ATTR_NAME) == agentJson.end() || agentJson.find(VA_JSON_ATTR_API) == agentJson.end() || agentJson.find(VA_JSON_ATTR_WWS) == agentJson.end() || agentJson.find(VA_JSON_ATTR_ACTIVE_WW) == agentJson.end() || agentJson.find(VA_JSON_ATTR_DESCRIPTION) == agentJson.end() || agentJson.find(VA_JSON_ATTR_VENDOR) == agentJson.end()) { stringstream error; error << "loadVoiceAgentConfig: One or more missing params in agent " "config " << agentJson.dump(); sLogger->log(Level::WARNING, TAG, error.str().c_str()); return false; } std::string id(agentJson[VA_JSON_ATTR_ID].get()); std::string name(agentJson[VA_JSON_ATTR_NAME].get()); std::string api(agentJson[VA_JSON_ATTR_API].get()); std::string description(agentJson[VA_JSON_ATTR_DESCRIPTION].get()); std::string vendor(agentJson[VA_JSON_ATTR_VENDOR].get()); std::string activeWakeword(agentJson[VA_JSON_ATTR_ACTIVE_WW].get()); bool isActive(agentJson[VA_JSON_ATTR_ACTIVE].get()); shared_ptr> wakewords = std::make_shared>(); json wakewordsJson = agentJson[VA_JSON_ATTR_WWS]; for (auto wwIt = wakewordsJson.begin(); wwIt != wakewordsJson.end(); ++wwIt) { wakewords->insert(wwIt->get()); } if (sAfbApi->requireApi(api, true) != 0) { stringstream error; error << "loadVoiceAgentConfig: Failed to require API \"" << api << "\" for voiceagent \"" << name << "\"."; sLogger->log(Level::ERROR, TAG, error.str().c_str()); return false; } if (AddVoiceAgentEvents(api) != 0) { stringstream error; error << "loadVoiceAgentConfig: Failed to add events for voiceagent \"" << name << "\"."; sLogger->log(Level::ERROR, TAG, error.str().c_str()); return false; } return sVoiceAgentsDataManager->addNewVoiceAgent( id, name, description, api, vendor, activeWakeword, isActive, wakewords); } // Helper function to parse voiceagents configuration static void parseVoiceAgentsConfig(const json &agentsConfigJson, std::string &defaultVoiceAgent) { if (agentsConfigJson.find(VA_JSON_ATTR_AGENTS) != agentsConfigJson.end()) { json agentsJson = agentsConfigJson[VA_JSON_ATTR_AGENTS]; for (auto agentIt = agentsJson.begin(); agentIt != agentsJson.end(); ++agentIt) { json agentJson = *agentIt; parseVoiceAgentConfig(agentJson); } } // Save the default agent if specified if (agentsConfigJson.find(VA_JSON_ATTR_DEFAULT) != agentsConfigJson.end()) { defaultVoiceAgent = agentsConfigJson[VA_JSON_ATTR_DEFAULT].get(); } } CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: Voice service not initialized."); return -1; } std::string defaultVoiceAgent; if (argsJ != nullptr) { // Parse any agent configs from controller arguments json agentsConfigJson = json::parse(json_object_to_json_string(argsJ)); parseVoiceAgentsConfig(agentsConfigJson, defaultVoiceAgent); } // Load external configuration if present filesystem::path agentsConfigFile = CONFIG_JSON_FILE; if(filesystem::exists(agentsConfigFile)) { stringstream message; message << "loadVoiceAgentsConfig: reading " << agentsConfigFile.string();; sLogger->log(Level::INFO, TAG, message.str().c_str()); ifstream agentsConfigJsonFile(agentsConfigFile.string()); json agentsConfigJson; agentsConfigJsonFile >> agentsConfigJson; parseVoiceAgentsConfig(agentsConfigJson, defaultVoiceAgent); } // Load any external individual voiceagent configurations error_code ec; for (const auto& entry : filesystem::directory_iterator(VA_CONFIG_JSON_DIR, ec)) { if (entry.is_regular_file(ec) && entry.path().extension().string() == JSON_EXT) { const auto filename = entry.path().string(); stringstream message; message << "loadVoiceAgentsConfig: reading " << filename; sLogger->log(Level::INFO, TAG, message.str().c_str()); ifstream jsonFile(filename); json agentJson; jsonFile >> agentJson; parseVoiceAgentConfig(agentJson); } } // Set the default agent if specified if (!defaultVoiceAgent.empty()) { stringstream message; message << "loadVoiceAgentsConfig: setting default agent to " << defaultVoiceAgent; sLogger->log(Level::INFO, TAG, message.str().c_str()); sVoiceAgentsDataManager->setDefaultVoiceAgent(defaultVoiceAgent); } else { sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: No default agent found"); } // Always return zero so service will start return 0; } CTLP_CAPI(startListening, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } if (sVRRequestProcessor == nullptr) { return -1; } int result = 0; std::string requestId = sVRRequestProcessor->startListening(); if (!requestId.empty()) { json responseJson; responseJson[STARTLISTENING_JSON_ATTR_REQUEST] = requestId; afb_req_success(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); } else { afb_req_fail(source->request, NULL, "Failed to startListening..."); } return 0; } CTLP_CAPI(cancelListening, source, argsJ, eventJ) { return 0; } CTLP_CAPI(enumerateVoiceAgents, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } auto agents = sVoiceAgentsDataManager->getAllVoiceAgents(); std::string defaultAgentId(sVoiceAgentsDataManager->getDefaultVoiceAgent()); json responseJson; json agentsJson = json::array(); for (auto agent : agents) { json agentJson; agentJson[VA_JSON_ATTR_ID] = agent->getId(); agentJson[VA_JSON_ATTR_NAME] = agent->getName(); agentJson[VA_JSON_ATTR_DESCRIPTION] = agent->getDescription(); agentJson[VA_JSON_ATTR_API] = agent->getApi(); agentJson[VA_JSON_ATTR_VENDOR] = agent->getVendor(); agentJson[VA_JSON_ATTR_ACTIVE] = agent->isActive(); agentJson[VA_JSON_ATTR_ACTIVE_WW] = agent->getActiveWakeword(); auto wakewords = agent->getWakeWords(); if (wakewords != nullptr) { json wakewordsJson; for (auto wakeword : *wakewords) { wakewordsJson.push_back(wakeword); } agentJson[VA_JSON_ATTR_WWS] = wakewordsJson; } agentsJson.push_back(agentJson); } responseJson[VA_JSON_ATTR_AGENTS] = agentsJson; responseJson[VA_JSON_ATTR_DEFAULT] = defaultAgentId; afb_req_success(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); return 0; } CTLP_CAPI(subscribe, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } if (eventJ == nullptr) { sLogger->log(Level::WARNING, TAG, "subscribe: No arguments supplied."); return -1; } json subscribeJson = json::parse(json_object_to_json_string(eventJ)); if (subscribeJson.find(EVENTS_JSON_ATTR_VA_ID) == subscribeJson.end()) { sLogger->log(Level::ERROR, TAG, "subscribe: No voiceagent id found in subscribe json"); return -1; } std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get()); if (subscribeJson.find(EVENTS_JSON_ATTR_EVENTS) == subscribeJson.end()) { sLogger->log(Level::ERROR, TAG, "subscribe: No events array found in subscribe json"); return -1; } std::list events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get>()); // Subscribe this client for the listed events. auto request = vshlcore::afb::AFBRequestImpl::create(source->request); for (auto event : events) { if (!sVoiceAgentsDataManager->subscribeToVshlEventFromVoiceAgent(*request, event, voiceAgentId)) { sLogger->log(Level::ERROR, TAG, "subscribe: Failed to subscribe to event: " + event); return -1; } } afb_req_success(source->request, json_object_new_string("Subscription to events successfully completed."), NULL); return 0; } CTLP_CAPI(setDefaultVoiceAgent, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } if (eventJ == nullptr) { sLogger->log(Level::WARNING, TAG, "setDefaultVoiceAgent: No arguments supplied."); return -1; } json jsonRequest = json::parse(json_object_to_json_string(eventJ)); if (jsonRequest.find(VA_JSON_ATTR_ID) == jsonRequest.end()) { sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: voice agent id not found in request json"); return -1; } std::string voiceAgentId(jsonRequest[VA_JSON_ATTR_ID].get()); if (!sVoiceAgentsDataManager->setDefaultVoiceAgent(voiceAgentId)) { sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: Failed to set default agent"); return -1; } afb_req_success(source->request, NULL, NULL); return 0; } CTLP_CAPI(subscribeToLoginEvents, source, argsJ, eventJ) { if (sVoiceAgentsDataManager == nullptr) { return -1; } if (eventJ == nullptr) { sLogger->log(Level::WARNING, TAG, "subscribeToLoginEvents: No arguments supplied."); return -1; } if (sVRRequestProcessor == nullptr) { return -1; } json subscribeJson = json::parse(json_object_to_json_string(eventJ)); if (subscribeJson.find(EVENTS_JSON_ATTR_VA_ID) == subscribeJson.end()) { sLogger->log(Level::ERROR, TAG, "subscribeToLoginEvents: No voiceagent id found in subscribe json"); return -1; } std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get()); if (subscribeJson.find(EVENTS_JSON_ATTR_EVENTS) == subscribeJson.end()) { sLogger->log(Level::ERROR, TAG, "subscribeToLoginEvents: No events array found in subscribe json"); return -1; } std::list events(subscribeJson[EVENTS_JSON_ATTR_EVENTS].get>()); int result = 0; std::string requestId = sVRRequestProcessor->subscribeToLoginEvents(voiceAgentId, &events); if (!requestId.empty()) { json responseJson; responseJson[SUBSCRIBE2LOGINEVENTS_JSON_ATTR_REQUEST] = requestId; afb_req_success(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL); } else { afb_req_fail(source->request, NULL, "Failed to subscribeToLoginEvents..."); } return 0; }