From 0609c41e46291346455b5f06b3e6bb5e3958a0f2 Mon Sep 17 00:00:00 2001 From: Vijay Selvaraj Date: Thu, 14 May 2020 01:37:31 -0400 Subject: [PATCH] Added AVInput, Bluetooth, DataCapture, Network, RemoteActionMapping, ScreenCapture and XCast plugins --- AVInput/AVInput.config | 3 + AVInput/AVInput.cpp | 401 +++ AVInput/AVInput.h | 95 + AVInput/CMakeLists.txt | 52 + AVInput/Module.cpp | 22 + AVInput/Module.h | 29 + AVInput/README.md | 35 + Bluetooth/Bluetooth.config | 3 + Bluetooth/Bluetooth.cpp | 1528 +++++++++++ Bluetooth/Bluetooth.h | 217 ++ Bluetooth/CMakeLists.txt | 51 + Bluetooth/Module.cpp | 22 + Bluetooth/Module.h | 29 + Bluetooth/README.md | 113 + Bluetooth/cmake/FindBTMGR.cmake | 44 + DataCapture/CMakeLists.txt | 62 + DataCapture/DataCapture.config | 3 + DataCapture/DataCapture.cpp | 538 ++++ DataCapture/DataCapture.h | 79 + DataCapture/Module.cpp | 22 + DataCapture/Module.h | 29 + DataCapture/README.md | 27 + DataCapture/socket_adaptor.cpp | 447 ++++ DataCapture/socket_adaptor.h | 149 ++ Network/CMakeLists.txt | 45 + Network/Module.cpp | 22 + Network/Module.h | 29 + Network/NetUtils.cpp | 1171 +++++++++ Network/NetUtils.h | 178 ++ Network/NetUtilsNetlink.cpp | 377 +++ Network/NetUtilsNetlink.h | 64 + Network/Network.config | 3 + Network/Network.cpp | 650 +++++ Network/Network.h | 117 + Network/NetworkTraceroute.cpp | 134 + Network/PingNotifier.cpp | 226 ++ Network/README.md | 35 + RemoteActionMapping/CMakeLists.txt | 53 + RemoteActionMapping/Module.cpp | 22 + RemoteActionMapping/Module.h | 29 + RemoteActionMapping/README.md | 55 + RemoteActionMapping/RamHelper.cpp | 1827 +++++++++++++ RemoteActionMapping/RamHelper.h | 308 +++ .../RemoteActionMapping.config | 3 + RemoteActionMapping/RemoteActionMapping.cpp | 2310 +++++++++++++++++ RemoteActionMapping/RemoteActionMapping.h | 203 ++ RemoteActionMapping/cmake/FindCTRLM.cmake | 36 + RemoteActionMapping/cmake/FindIARMBus.cmake | 45 + RemoteActionMapping/test/CMakeLists.txt | 32 + RemoteActionMapping/test/Module.h | 27 + RemoteActionMapping/test/ramTestClient.cpp | 809 ++++++ ScreenCapture/CMakeLists.txt | 39 + ScreenCapture/Module.cpp | 22 + ScreenCapture/Module.h | 29 + ScreenCapture/README.md | 15 + ScreenCapture/ScreenCapture.config | 4 + ScreenCapture/ScreenCapture.cpp | 497 ++++ ScreenCapture/ScreenCapture.h | 119 + XCast/CMakeLists.txt | 43 + XCast/Module.cpp | 22 + XCast/Module.h | 29 + XCast/README.md | 13 + XCast/XCast.config | 3 + XCast/XCast.cpp | 451 ++++ XCast/XCast.h | 96 + 65 files changed, 14192 insertions(+) create mode 100644 AVInput/AVInput.config create mode 100644 AVInput/AVInput.cpp create mode 100644 AVInput/AVInput.h create mode 100644 AVInput/CMakeLists.txt create mode 100644 AVInput/Module.cpp create mode 100644 AVInput/Module.h create mode 100644 AVInput/README.md create mode 100644 Bluetooth/Bluetooth.config create mode 100644 Bluetooth/Bluetooth.cpp create mode 100644 Bluetooth/Bluetooth.h create mode 100644 Bluetooth/CMakeLists.txt create mode 100644 Bluetooth/Module.cpp create mode 100644 Bluetooth/Module.h create mode 100644 Bluetooth/README.md create mode 100644 Bluetooth/cmake/FindBTMGR.cmake create mode 100644 DataCapture/CMakeLists.txt create mode 100644 DataCapture/DataCapture.config create mode 100644 DataCapture/DataCapture.cpp create mode 100644 DataCapture/DataCapture.h create mode 100644 DataCapture/Module.cpp create mode 100644 DataCapture/Module.h create mode 100644 DataCapture/README.md create mode 100644 DataCapture/socket_adaptor.cpp create mode 100644 DataCapture/socket_adaptor.h create mode 100644 Network/CMakeLists.txt create mode 100644 Network/Module.cpp create mode 100644 Network/Module.h create mode 100644 Network/NetUtils.cpp create mode 100644 Network/NetUtils.h create mode 100644 Network/NetUtilsNetlink.cpp create mode 100644 Network/NetUtilsNetlink.h create mode 100644 Network/Network.config create mode 100644 Network/Network.cpp create mode 100644 Network/Network.h create mode 100644 Network/NetworkTraceroute.cpp create mode 100644 Network/PingNotifier.cpp create mode 100644 Network/README.md create mode 100644 RemoteActionMapping/CMakeLists.txt create mode 100644 RemoteActionMapping/Module.cpp create mode 100644 RemoteActionMapping/Module.h create mode 100644 RemoteActionMapping/README.md create mode 100644 RemoteActionMapping/RamHelper.cpp create mode 100644 RemoteActionMapping/RamHelper.h create mode 100644 RemoteActionMapping/RemoteActionMapping.config create mode 100644 RemoteActionMapping/RemoteActionMapping.cpp create mode 100644 RemoteActionMapping/RemoteActionMapping.h create mode 100644 RemoteActionMapping/cmake/FindCTRLM.cmake create mode 100644 RemoteActionMapping/cmake/FindIARMBus.cmake create mode 100644 RemoteActionMapping/test/CMakeLists.txt create mode 100644 RemoteActionMapping/test/Module.h create mode 100644 RemoteActionMapping/test/ramTestClient.cpp create mode 100644 ScreenCapture/CMakeLists.txt create mode 100644 ScreenCapture/Module.cpp create mode 100644 ScreenCapture/Module.h create mode 100644 ScreenCapture/README.md create mode 100644 ScreenCapture/ScreenCapture.config create mode 100644 ScreenCapture/ScreenCapture.cpp create mode 100644 ScreenCapture/ScreenCapture.h create mode 100644 XCast/CMakeLists.txt create mode 100644 XCast/Module.cpp create mode 100644 XCast/Module.h create mode 100644 XCast/README.md create mode 100644 XCast/XCast.config create mode 100644 XCast/XCast.cpp create mode 100644 XCast/XCast.h diff --git a/AVInput/AVInput.config b/AVInput/AVInput.config new file mode 100644 index 0000000000..ad7fb450db --- /dev/null +++ b/AVInput/AVInput.config @@ -0,0 +1,3 @@ +set (autostart false) +set (preconditions Platform) +set (callsign com.comcast.AVInput) diff --git a/AVInput/AVInput.cpp b/AVInput/AVInput.cpp new file mode 100644 index 0000000000..3f487db0bf --- /dev/null +++ b/AVInput/AVInput.cpp @@ -0,0 +1,401 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "AVInput.h" +#include "hdmiIn.hpp" + +const short WPEFramework::Plugin::AVInput::API_VERSION_NUMBER_MAJOR = 1; +const short WPEFramework::Plugin::AVInput::API_VERSION_NUMBER_MINOR = 0; +const string WPEFramework::Plugin::AVInput::SERVICE_NAME = "com.comcast.AVInput"; +//methods +const string WPEFramework::Plugin::AVInput::METHOD_GET_API_VERSION_NUMBER = "getApiVersionNumber"; +const string WPEFramework::Plugin::AVInput::AVINPUT_METHOD_NUMBER_OF_INPUTS = "numberOfInputs"; +const string WPEFramework::Plugin::AVInput::AVINPUT_METHOD_CURRENT_VIDEO_MODE = "currentVideoMode"; +const string WPEFramework::Plugin::AVInput::AVINPUT_METHOD_CONTENT_PROTECTED = "contentProtected"; +//events +const string WPEFramework::Plugin::AVInput::AVINPUT_EVENT_ON_AV_INPUT_ACTIVE = "onAVInputActive"; +const string WPEFramework::Plugin::AVInput::AVINPUT_EVENT_ON_AV_INPUT_INACTIVE = "onAVInputInactive"; + +#define SUBSCRIPTION_CALLSIGN "com.comcast.HdmiInput" +#define SUBSCRIPTION_CALLSIGN_VER SUBSCRIPTION_CALLSIGN".1" +#define SUBSCRIPTION_EVENT "onDevicesChanged" +#define SERVER_DETAILS "127.0.0.1:9998" +#define WARMING_UP_TIME_IN_SECONDS 5 +#define RECONNECTION_TIME_IN_MILLISECONDS 5500 + +using namespace std; + +namespace WPEFramework { + namespace Plugin { + + SERVICE_REGISTRATION(AVInput, 1, 0); + + AVInput* AVInput::_instance = nullptr; + + AVInput::AVInput() + : AbstractPlugin() + , m_apiVersionNumber(API_VERSION_NUMBER_MAJOR) + , m_client(nullptr) + , m_subscribed(false) + { + LOGINFO("ctor"); + AVInput::_instance = this; + registerMethod(METHOD_GET_API_VERSION_NUMBER, &AVInput::getApiVersionNumber, this); + registerMethod(AVINPUT_METHOD_NUMBER_OF_INPUTS, &AVInput::numberOfInputsWrapper, this); + registerMethod(AVINPUT_METHOD_CURRENT_VIDEO_MODE, &AVInput::currentVideoModeWrapper, this); + registerMethod(AVINPUT_METHOD_CONTENT_PROTECTED, &AVInput::contentProtectedWrapper, this); + + m_timer.connect(std::bind(&AVInput::onTimer, this)); + } + + AVInput::~AVInput() + { + LOGINFO("dtor"); + AVInput::_instance = nullptr; + } + + const string AVInput::Initialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + + if(m_timer.isActive()) { + m_timer.stop(); + } + + activatePlugin(SUBSCRIPTION_CALLSIGN); + LOGINFO("Starting the timer"); + m_timer.start(RECONNECTION_TIME_IN_MILLISECONDS); + return ""; + } + + void AVInput::Deinitialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + } + + string AVInput::Information() const + { + return(string("{\"service\": \"") + SERVICE_NAME + string("\"}")); + } + + // Registered methods begin + uint32_t AVInput::getApiVersionNumber(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + UNUSED(parameters); + response["version"] = m_apiVersionNumber; + returnResponse(true); + } + + uint32_t AVInput::numberOfInputsWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + bool success; + if (getActivatedPluginReady(SUBSCRIPTION_CALLSIGN)) + { + response["numberOfInputs"] = numberOfInputs(&success); + response["message"] = "Success"; + } else { + success = false; + response["message"] = string(SUBSCRIPTION_CALLSIGN) + " plugin is not ready"; + } + returnResponse(success); + } + + uint32_t AVInput::currentVideoModeWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + bool success; + if (getActivatedPluginReady(SUBSCRIPTION_CALLSIGN)) + { + response["currentVideoMode"] = currentVideoMode(); + response["message"] = "Success"; + } else { + success = false; + response["message"] = string(SUBSCRIPTION_CALLSIGN) + " plugin is not ready"; + } + returnResponse(success); + } + + uint32_t AVInput::contentProtectedWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + response["isContentProtected"] = contentProtected(); + + returnResponse(true); + } + // Registered methods begin + + // Events begin + void AVInput::onAVInputActive(JsonObject& url) + { + LOGINFO(); + sendNotify(C_STR(AVINPUT_EVENT_ON_AV_INPUT_ACTIVE), url); + } + void AVInput::onAVInputInactive(JsonObject& url) + { + LOGINFO(); + sendNotify(C_STR(AVINPUT_EVENT_ON_AV_INPUT_INACTIVE), url); + } + // Events end + + // Internal methods begin + int AVInput::numberOfInputs(bool *pSuccess) + { + int res = 0; + LOGINFO("Invoking device::HdmiInput::getInstance().GetNumberOfInputs()"); + try + { + res = device::HdmiInput::getInstance().getNumberOfInputs();; + } + catch (...) + { + LOGERR("Exception caught"); + if (pSuccess) { + *pSuccess = false; + } + } + if (pSuccess) { + *pSuccess = true; + } + return res; + } + + string AVInput::currentVideoMode(bool *pSuccess) + { + string res; + LOGINFO("Invoking device::HdmiInput::getInstance().getCurrentVideoMode()"); + try + { + res = device::HdmiInput::getInstance().getCurrentVideoMode(); + } + catch (...) + { + LOGERR("Exception caught"); + if (pSuccess) { + *pSuccess = false; + } + } + if (pSuccess) { + *pSuccess = true; + } + + return res; + } + + bool AVInput::contentProtected() + { + // Ths is the way it's done in Service Manager + // We can get the "content protected" attribute from HDCP, but does it cover the Hdmi In stream? + return true; + } + + // Thunder plugins communication + std::shared_ptr > AVInput::getThunderControllerClient() + { + // making function local static to be thread safe + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(SERVER_DETAILS))); + static std::shared_ptr > thunderClient = make_shared >("", ""); + return thunderClient; + } + + void AVInput::activatePlugin(const char* callSign) + { + JsonObject joParams; + joParams.Set("callsign",callSign); + JsonObject joResult; + + if(!isPluginActivated(callSign)) + { + LOGINFO("Activating %s", callSign); + // deactivatePlugin() would have "deactivate" as a command (plus, m_activatedPlugins should have an entry erased from it, see isPluginActivated()) + // setting wait Time to 2 seconds + uint32_t status = getThunderControllerClient()->Invoke(2000, "activate", joParams, joResult); + string strParams; + string strResult; + joParams.ToString(strParams); + joResult.ToString(strResult); + LOGINFO("Called method %s, with params %s, status: %d, result: %s" + , "activate" + , C_STR(strParams) + , status + , C_STR(strResult)); + if (status == Core::ERROR_NONE) + { + time_t endTime = time(NULL) + WARMING_UP_TIME_IN_SECONDS; + LOGINFO("Adding %s to the list of active plugins. Should be ready in about %d seconds", callSign, WARMING_UP_TIME_IN_SECONDS); + m_activatedPlugins[string(callSign)] = endTime; + + } + } + } + + bool AVInput::isPluginActivated(const char* callSign) + { + string method = "status@" + string(callSign); + Core::JSON::ArrayType joResult; + getThunderControllerClient()->Get >(2000, method.c_str(),joResult); + LOGINFO("Getting status for callSign %s, result: %s", callSign, joResult[0].JSONState.Data().c_str()); + bool pluginActivated = joResult[0].JSONState == PluginHost::IShell::ACTIVATED; + if(!pluginActivated) + { + auto p = m_activatedPlugins.find(string(callSign)); + if (p != m_activatedPlugins.end()) + { + LOGWARN("Previoulsly active plugin %s appers to be deactivated, removing from the list", callSign); + m_activatedPlugins.erase(p); + } + } + return pluginActivated; + } + + bool AVInput::getActivatedPluginReady(const char* callSign) + { + bool res = false; + + auto p = m_activatedPlugins.find(string(callSign)); + if (p != m_activatedPlugins.end()) + { + time_t endTime = p->second; + time_t nowTime = time(NULL); + time_t diffTime = endTime - nowTime; + + if (diffTime > 0) { + LOGINFO("Waiting about %ld second(s) for %s to warm up ", diffTime, callSign); + sleep(diffTime); + res = true; + } else { + res = true; + } + } else { + LOGERR("Plugin %s has not been activated yet. Call activatePlugin() first!", callSign); + } + return res; + } + // Thunder plugins communication end + + // Event management + // 1. + uint32_t AVInput::subscribe(const char* callSignVer, const char* eventName) + { + uint32_t err = Core::ERROR_NONE; + LOGINFO("Attempting to subscribe for event"); + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(SERVER_DETAILS))); + if (nullptr == m_client) { + m_client = make_shared>(_T(callSignVer), (_T(callSignVer))); + if (nullptr == m_client) { + LOGERR("JSONRPC: %s: client initialization failed", callSignVer); + err = Core::ERROR_UNAVAILABLE; + } else { + /* Register handlers for Event reception. */ + err =m_client->Subscribe(1000, eventName + , &AVInput::onDevicesChangedEventHandler, this); + if ( err == Core::ERROR_NONE) { + LOGINFO("Subscribed for %s", eventName); + } else { + LOGERR("Failed to subscribe for %s with code %d", eventName, err); + } + } + } + return err; + } + + // 2. + void AVInput::onDevicesChangedEventHandler(const JsonObject& parameters) { + JsonArray devices; + string message; + + parameters.ToString(message); + LOGINFO("[onDevicesChanged event], %s : %s", __FUNCTION__, C_STR(message)); + + if (parameters.HasLabel("devices")) { + devices = parameters["devices"].Array(); + + for (int i = 0; i < devices.Length(); ++i) + { + JsonObject device = devices[i].Object(); + JsonObject url; + bool connected; + int id; + + if (device.HasLabel("connected")) { + if(device["connected"].String() == "true") { + connected = true; + } else { + connected = false; + } + } else { + connected = false; + LOGERR("Field 'connected' could not be found in the event's payload. Assuming false"); + } + + if (device.HasLabel("id")) { + getNumberParameter("id", id); + } else { + id = 0; + LOGERR("Field 'id' could not be found in the event's payload. Assuming 0"); + } + + url["url"] = string("avin://input") + std::to_string(id); + + if (connected) { + onAVInputActive(url); + } else { + onAVInputInactive(url); + } + } + } else { + LOGERR("Field 'devices' could not be found in the event's payload."); + } + } + + // 3. + void AVInput::onTimer() + { + std::lock_guard guard(m_callMutex); + LOGINFO(); + bool pluginActivated = isPluginActivated(SUBSCRIPTION_CALLSIGN); + if(!m_subscribed) { + if (pluginActivated && subscribe(SUBSCRIPTION_CALLSIGN_VER, SUBSCRIPTION_EVENT) == Core::ERROR_NONE) + { + m_subscribed = true; + if (m_timer.isActive()) { + m_timer.stop(); + LOGINFO("Timer stopped."); + } + LOGINFO("Subscription completed."); + } else { + LOGERR("Could not subscribe this time, one more attempt in %d msec. Plugin is %s", RECONNECTION_TIME_IN_MILLISECONDS, pluginActivated ? "ACTIVE" : "BLOCKED"); + if (!pluginActivated) + { + activatePlugin(SUBSCRIPTION_CALLSIGN); + } + } + } else { + // Not supposed to be here + LOGINFO("Already subscribed. Stopping the timer."); + if (m_timer.isActive()) { + m_timer.stop(); + } + } + } + // Event management end + // Internal methods end + } // namespace Plugin +} // namespace WPEFramework diff --git a/AVInput/AVInput.h b/AVInput/AVInput.h new file mode 100644 index 0000000000..95a9321fda --- /dev/null +++ b/AVInput/AVInput.h @@ -0,0 +1,95 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include +#include "Module.h" +#include "utils.h" +#include "AbstractPlugin.h" +#include +#include "tptimer.h" + +namespace WPEFramework { + + namespace Plugin { + + class AVInput : public AbstractPlugin { + public: + AVInput(); + virtual ~AVInput(); + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override; + + public/*members*/: + static AVInput* _instance; + + public /*constants*/: + static const short API_VERSION_NUMBER_MAJOR; + static const short API_VERSION_NUMBER_MINOR; + static const string SERVICE_NAME; + //methods + static const string METHOD_GET_API_VERSION_NUMBER; + static const string AVINPUT_METHOD_NUMBER_OF_INPUTS; + static const string AVINPUT_METHOD_CURRENT_VIDEO_MODE; + static const string AVINPUT_METHOD_CONTENT_PROTECTED; + //events + static const string AVINPUT_EVENT_ON_AV_INPUT_ACTIVE; + static const string AVINPUT_EVENT_ON_AV_INPUT_INACTIVE; + + private/*registered methods*/: + // Note: `JsonObject& parameters` corresponds to `params` in JSON RPC call + //methods + uint32_t getApiVersionNumber(const JsonObject& parameters, JsonObject& response); + uint32_t numberOfInputsWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t currentVideoModeWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t contentProtectedWrapper(const JsonObject& parameters, JsonObject& response); + //Events + void onAVInputActive(JsonObject& url); + void onAVInputInactive(JsonObject& url); + void onDevicesChangedEventHandler(const JsonObject& parameters); + + private/*internal methods*/: + AVInput(const AVInput&) = delete; + AVInput& operator=(const AVInput&) = delete; + + std::shared_ptr> getThunderControllerClient(); + void activatePlugin(const char* callSign); + bool isPluginActivated(const char* callSign); + bool getActivatedPluginReady(const char* callSign); + + int numberOfInputs(bool* pSuccess = nullptr); + string currentVideoMode(bool* pSuccess = nullptr); + bool contentProtected(); + + uint32_t subscribe(const char* callSignVer, const char* eventName); + void onTimer(); + + private/*members*/: + uint32_t m_apiVersionNumber; + std::shared_ptr > m_client; + TpTimer m_timer; + // A plugin we're depending on may require some time to warm up after activation; readyTimeInTicks is an estimation of that moment + std::map m_activatedPlugins; + bool m_subscribed; + std::mutex m_callMutex; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/AVInput/CMakeLists.txt b/AVInput/CMakeLists.txt new file mode 100644 index 0000000000..467870688a --- /dev/null +++ b/AVInput/CMakeLists.txt @@ -0,0 +1,52 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME AVInput) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) +find_package(${NAMESPACE}Protocols REQUIRED) + +add_library(${MODULE_NAME} SHARED + AVInput.cpp + Module.cpp + ../helpers/tptimer.cpp + ) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +target_link_libraries(${MODULE_NAME} + PRIVATE + ${NAMESPACE}Protocols::${NAMESPACE}Protocols + ) +find_package(DS) +find_package(IARMBus) + +target_include_directories(${MODULE_NAME} PRIVATE ${DS_INCLUDE_DIRS}) +target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +target_link_libraries(${MODULE_NAME} PUBLIC ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES} ${DS_LIBRARIES} ) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/AVInput/Module.cpp b/AVInput/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/AVInput/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/AVInput/Module.h b/AVInput/Module.h new file mode 100644 index 0000000000..f918abb82e --- /dev/null +++ b/AVInput/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME AVInput +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/AVInput/README.md b/AVInput/README.md new file mode 100644 index 0000000000..11a13b9e99 --- /dev/null +++ b/AVInput/README.md @@ -0,0 +1,35 @@ +----------------- +# AVInput + +## Versions +`com.comcast.AVInput.1` + +## Methods: +``` +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.AVInput.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.AVInput.1.numberOfInputs"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.AVInput.1.currentVideoMode"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.AVInput.1.contentProtected"}' http://127.0.0.1:9998/jsonrpc +``` +## Responses +``` +{"jsonrpc":"2.0","id":3,"result":{"version":1,"success":true}} +{"jsonrpc":"2.0","id":3,"result":{"numberOfInputs":1,"success":true}] +{"jsonrpc":"2.0","id":3,"result":{"currentVideoMode":"unknownp","success":true}} +{"jsonrpc":"2.0","id":3,"result":{"isContentProtected":true,"success":true}} +``` + +## Events +``` +onAVInputActive +onAVInputInactive +``` +## Events logged +``` +onAVInputActive: Notify onAVInputActive {"url":"avin://input0"} +onAVInputInactive: Notify onAVInputInactive {"url":"avin://input0"} +``` + +## Full Reference +https://etwiki.sys.comcast.net/display/RDK/AVInput + diff --git a/Bluetooth/Bluetooth.config b/Bluetooth/Bluetooth.config new file mode 100644 index 0000000000..4d7adab544 --- /dev/null +++ b/Bluetooth/Bluetooth.config @@ -0,0 +1,3 @@ +set (autostart false) +set (preconditions Platform) +set (callsign "org.rdk.Bluetooth") diff --git a/Bluetooth/Bluetooth.cpp b/Bluetooth/Bluetooth.cpp new file mode 100644 index 0000000000..968e59d6ef --- /dev/null +++ b/Bluetooth/Bluetooth.cpp @@ -0,0 +1,1528 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 + +#include "Bluetooth.h" + +#include + +// IMPLEMENTATION NOTE +// +// Bluetooth Settings API in Thunder follows the schema proposed by Metrological what differs from the underlying +// Bluetooth Manager API in RDK, which the plugin calls. +// As a result, the exposed (registered) methods are implemented as wrappers that follow the Metrological notation. The wrappers then call the private methods +// with RDK naming schema which, in turn, call to actual Bluetooth Manager functions. These "internal" methods are similar to what we had in Service Manager as public APIs. +// For example, the exposed "startScan" method is mapped to "startScanWrapper()" and that one calls to "startDeviceDiscovery()" internally, +// which finally calls to "BTRMGR_StartDeviceDiscovery()" in Bluetooth Manager. + +const short WPEFramework::Plugin::Bluetooth::API_VERSION_NUMBER_MAJOR = 1; // corresponds to org.rdk.Bluetooth_5 +const short WPEFramework::Plugin::Bluetooth::API_VERSION_NUMBER_MINOR = 0; +const string WPEFramework::Plugin::Bluetooth::SERVICE_NAME = "org.rdk.Bluetooth"; +const string WPEFramework::Plugin::Bluetooth::METHOD_START_SCAN = "startScan"; +const string WPEFramework::Plugin::Bluetooth::METHOD_STOP_SCAN = "stopScan"; +const string WPEFramework::Plugin::Bluetooth::METHOD_IS_DISCOVERABLE = "isDiscoverable"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_DISCOVERED_DEVICES = "getDiscoveredDevices"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_PAIRED_DEVICES = "getPairedDevices"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_CONNECTED_DEVICES = "getConnectedDevices"; +const string WPEFramework::Plugin::Bluetooth::METHOD_CONNECT = "connect"; +const string WPEFramework::Plugin::Bluetooth::METHOD_DISCONNECT = "disconnect"; +const string WPEFramework::Plugin::Bluetooth::METHOD_SET_AUDIO_STREAM = "setAudioStream"; +const string WPEFramework::Plugin::Bluetooth::METHOD_PAIR = "pair"; +const string WPEFramework::Plugin::Bluetooth::METHOD_UNPAIR = "unpair"; +const string WPEFramework::Plugin::Bluetooth::METHOD_ENABLE = "enable"; +const string WPEFramework::Plugin::Bluetooth::METHOD_DISABLE = "disable"; +const string WPEFramework::Plugin::Bluetooth::METHOD_SET_DISCOVERABLE = "setDiscoverable"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_NAME = "getName"; +const string WPEFramework::Plugin::Bluetooth::METHOD_SET_NAME = "setName"; +const string WPEFramework::Plugin::Bluetooth::METHOD_SET_AUDIO_PLAYBACK_COMMAND = "sendAudioPlaybackCommand"; +const string WPEFramework::Plugin::Bluetooth::METHOD_SET_EVENT_RESPONSE = "respondToEvent"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_DEVICE_INFO = "getDeviceInfo"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_AUDIO_INFO = "getAudioInfo"; +const string WPEFramework::Plugin::Bluetooth::METHOD_GET_API_VERSION_NUMBER = "getApiVersionNumber"; + +const string WPEFramework::Plugin::Bluetooth::EVT_STATUS_CHANGED = "onStatusChanged"; +const string WPEFramework::Plugin::Bluetooth::EVT_PAIRING_REQUEST = "onPairingRequest"; +const string WPEFramework::Plugin::Bluetooth::EVT_REQUEST_FAILED = "onRequestFailed"; +const string WPEFramework::Plugin::Bluetooth::EVT_CONNECTION_REQUEST = "onConnectionRequest"; +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_REQUEST = "onPlaybackRequest"; + +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_STARTED = "onPlaybackChange"; // action: started +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_PAUSED = "onPlaybackChange"; // action: paused +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_STOPPED = "onPlaybackChange"; // action: stopped +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_ENDED = "onPlaybackChange"; // action: paused + +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_POSITION = "onPlaybackProgress"; +const string WPEFramework::Plugin::Bluetooth::EVT_PLAYBACK_NEW_TRACK = "onPlaybackNewTrack"; +const string WPEFramework::Plugin::Bluetooth::EVT_DEVICE_FOUND = "onDeviceFound"; +const string WPEFramework::Plugin::Bluetooth::EVT_DEVICE_LOST_OR_OUT_OF_RANGE = "onDeviceLost"; +const string WPEFramework::Plugin::Bluetooth::EVT_DEVICE_DISCOVERY_UPDATE = "onDiscoveredDevice"; + +const string WPEFramework::Plugin::Bluetooth::STATUS_NO_BLUETOOTH_HARDWARE = "NO_BLUETOOTH_HARDWARE"; +const string WPEFramework::Plugin::Bluetooth::STATUS_SOFTWARE_DISABLED = "SOFTWARE_DISABLED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_AVAILABLE = "AVAILABLE"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_CONNECT = "CONNECT"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_DISCONNECT = "DISCONNECT"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_BLUETOOTH_ENABLED = "BLUETOOTH_ENABLED"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_BLUETOOTH_DISABLED = "BLUETOOTH_DISABLED"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_BLUETOOTH_INPUT_ENABLED = "BLUETOOTH_INPUT_ENABLED"; + +const string WPEFramework::Plugin::Bluetooth::ENABLE_PRIMARY_AUDIO = "PRIMARY"; +const string WPEFramework::Plugin::Bluetooth::ENABLE_AUXILIARY_AUDIO = "AUXILIARY"; +const string WPEFramework::Plugin::Bluetooth::STATUS_HARDWARE_AVAILABLE = "HARDWARE_AVAILABLE"; +const string WPEFramework::Plugin::Bluetooth::STATUS_HARDWARE_DISABLED = "HARDWARE_DISABLED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_SOFTWARE_ENABLED = "SOFTWARE_ENABLED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_SOFTWARE_INPUT_ENABLED = "SOFTWARE_INPUT_ENABLED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_PAIRING_CHANGE = "PAIRING_CHANGE"; +const string WPEFramework::Plugin::Bluetooth::STATUS_CONNECTION_CHANGE = "CONNECTION_CHANGE"; +const string WPEFramework::Plugin::Bluetooth::STATUS_DISCOVERY_STARTED = "DISCOVERY_STARTED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_DISCOVERY_COMPLETED = "DISCOVERY_COMPLETED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_PAIRING_FAILED = "PAIRING_FAILED"; +const string WPEFramework::Plugin::Bluetooth::STATUS_CONNECTION_FAILED= "CONNECTION_FAILED"; + +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_PLAY = "PLAY"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_STOP = "STOP"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_PAUSE = "PAUSE"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_RESUME = "RESUME"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_SKIP_NEXT = "SKIP_NEXT"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_SKIP_PREV = "SKIP_PREV"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_RESTART = "RESTART"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_VOLUME_UP = "VOLUME_UP"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_VOLUME_DOWN = "VOLUME_DOWN"; +const string WPEFramework::Plugin::Bluetooth::CMD_AUDIO_CTRL_MUTE = "AUDIO_MUTE"; + +namespace WPEFramework +{ + namespace Plugin + { + SERVICE_REGISTRATION(Bluetooth, Bluetooth::API_VERSION_NUMBER_MAJOR, Bluetooth::API_VERSION_NUMBER_MINOR); + + Bluetooth* Bluetooth::_instance = nullptr; + static Core::TimerType _discoveryTimer(64 * 1024, "DiscoveryTimer"); + + BTRMGR_Result_t bluetoothSrv_EventCallback (BTRMGR_EventMessage_t eventMsg) + { + if (!Bluetooth::_instance) { + LOGERR ("Invalid pointer. Bluetooth is not initialized (yet?). Event of type %d ignored.", eventMsg.m_eventType); + return BTRMGR_RESULT_INIT_FAILED; + } else { + Bluetooth::_instance->notifyEventWrapper(eventMsg); + return BTRMGR_RESULT_SUCCESS; + } + } + + Bluetooth::Bluetooth() + : AbstractPlugin() + , m_apiVersionNumber(API_VERSION_NUMBER_MAJOR) + , m_discoveryRunning(false) + , m_discoveryTimer(this) + { + LOGINFO(); + Bluetooth::_instance = this; + registerMethod(METHOD_GET_API_VERSION_NUMBER, &Bluetooth::getApiVersionNumber, this); + registerMethod(METHOD_START_SCAN, &Bluetooth::startScanWrapper, this); + registerMethod(METHOD_STOP_SCAN, &Bluetooth::stopScanWrapper, this); + registerMethod(METHOD_IS_DISCOVERABLE, &Bluetooth::isDiscoverableWrapper, this); + registerMethod(METHOD_GET_DISCOVERED_DEVICES, &Bluetooth::getDiscoveredDevicesWrapper, this); + registerMethod(METHOD_GET_PAIRED_DEVICES, &Bluetooth::getPairedDevicesWrapper, this); + registerMethod(METHOD_GET_CONNECTED_DEVICES, &Bluetooth::getConnectedDevicesWrapper, this); + registerMethod(METHOD_CONNECT, &Bluetooth::connectWrapper, this); + registerMethod(METHOD_DISCONNECT, &Bluetooth::disconnectWrapper, this); + registerMethod(METHOD_SET_AUDIO_STREAM, &Bluetooth::setAudioStreamWrapper, this); + registerMethod(METHOD_PAIR, &Bluetooth::pairWrapper, this); + registerMethod(METHOD_UNPAIR, &Bluetooth::unpairWrapper, this); + registerMethod(METHOD_ENABLE, &Bluetooth::enableWrapper, this); + registerMethod(METHOD_DISABLE, &Bluetooth::disableWrapper, this); + registerMethod(METHOD_SET_DISCOVERABLE, &Bluetooth::setDiscoverableWrapper, this); + registerMethod(METHOD_GET_NAME, &Bluetooth::getNameWrapper, this); + registerMethod(METHOD_SET_NAME, &Bluetooth::setNameWrapper, this); + registerMethod(METHOD_SET_AUDIO_PLAYBACK_COMMAND, &Bluetooth::sendAudioPlaybackCommandWrapper, this); + registerMethod(METHOD_SET_EVENT_RESPONSE, &Bluetooth::setEventResponseWrapper, this); + registerMethod(METHOD_GET_DEVICE_INFO, &Bluetooth::getDeviceInfoWrapper, this); + registerMethod(METHOD_GET_AUDIO_INFO, &Bluetooth::getMediaTrackInfoWrapper, this); + + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + rc = BTRMGR_Init(); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGWARN("Failed to init BTRMgr...!"); + } + else { + BTRMGR_RegisterEventCallback(bluetoothSrv_EventCallback); + } + } + + Bluetooth::~Bluetooth() + { + LOGINFO(); + Bluetooth::_instance = nullptr; + + if (m_executionThread.joinable()) + m_executionThread.join(); + } + + string Bluetooth::Information() const + { + return(string("{\"service\": \"") + SERVICE_NAME + string("\"}")); + } + + /// Internal methods begin + // + + // This method is not exposed due to the missing match in Metrological API + void Bluetooth::getStatusSupport(string& status) + { + unsigned char numOfAdapters = 0; + BTRMGR_Result_t rc = BTRMGR_GetNumberOfAdapters(&numOfAdapters); + status = STATUS_NO_BLUETOOTH_HARDWARE; //TODO: shall we introduce a more specific status? STATUS_BLUETOOTH_UNKNOWN? + + if (BTRMGR_RESULT_SUCCESS != rc) + LOGERR("Failed to get the number of adapters..!"); + + if (numOfAdapters) { + unsigned char power_status = 0; + rc = BTRMGR_GetAdapterPowerStatus(0, &power_status); + if (BTRMGR_RESULT_SUCCESS != rc) { + LOGERR("Failed to get the power status of adapter..!"); + status = STATUS_SOFTWARE_DISABLED; + } + + if (power_status) + status = STATUS_AVAILABLE; + else + status = STATUS_SOFTWARE_DISABLED; + } else + status = STATUS_NO_BLUETOOTH_HARDWARE; + LOGINFO("getStatusSupport: returning %s", C_STR(status)); + } + + bool Bluetooth::isAdapterDiscoverable() + { + unsigned char numOfAdapters = 0; + bool result = false; + BTRMGR_Result_t rc = BTRMGR_GetNumberOfAdapters(&numOfAdapters); + if (BTRMGR_RESULT_SUCCESS != rc) + LOGERR("Failed to get the number of adapters..!"); + if (numOfAdapters) { + unsigned char adapter_discoverable = 0; + rc = BTRMGR_IsAdapterDiscoverable(0, &adapter_discoverable); + if (BTRMGR_RESULT_SUCCESS != rc) { + LOGERR("Failed to get the discoverable status of adapter..!"); + adapter_discoverable = 0; + } + if(adapter_discoverable) { + result = true; + } else { + result = false; + } + } + return result; + } + + string Bluetooth::startDeviceDiscovery(int timeout, const string &discProfile) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + unsigned char numOfAdapters = 0; + + if (!m_discoveryRunning) + { + rc = BTRMGR_GetNumberOfAdapters(&numOfAdapters); + if (BTRMGR_RESULT_SUCCESS != rc) + LOGERR("Failed to get the number of adapters..!"); + + if (numOfAdapters) { + BTRMGR_DeviceOperationType_t lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_AUDIO_OUTPUT; + + if (Utils::String::contains(discProfile, "LOUDSPEAKER") || + Utils::String::contains(discProfile, "HEADPHONES") || + Utils::String::contains(discProfile, "WEARABLE HEADSET") || + Utils::String::contains(discProfile, "HIFI AUDIO DEVICE")) { + lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_AUDIO_OUTPUT; + } + else if (Utils::String::contains(discProfile, "SMARTPHONE") || + Utils::String::contains(discProfile, "TABLET")) { + lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_AUDIO_INPUT; + } + else if (Utils::String::contains(discProfile, "KEYBOARD") || + Utils::String::contains(discProfile, "MOUSE") || + Utils::String::contains(discProfile, "JOYSTICK")) { + lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_HID; + } + else if (Utils::String::contains(discProfile, "LE TILE") || + Utils::String::contains(discProfile, "LE")) { + lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_LE; + } + else if (Utils::String::contains(discProfile, "DEFAULT")) { + lenDevOpDiscType = BTRMGR_DEVICE_OP_TYPE_UNKNOWN; + } + + rc = BTRMGR_StartDeviceDiscovery(0, lenDevOpDiscType); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to start the discovery..!"); + } else { + LOGWARN("Started discovery..!"); + } + + /* Set the discovery flag */ + m_discoveryRunning = true; + if (timeout <= 0) + { + stopDeviceDiscovery(); + } + else + { + startDiscoveryTimer(timeout * 1000); + } + } + else + return STATUS_NO_BLUETOOTH_HARDWARE; + } + else + LOGWARN ("Discovery is in progress..!"); + + return STATUS_AVAILABLE; + } + + bool Bluetooth::stopDeviceDiscovery() + { + BTRMGR_Result_t rc = BTRMGR_RESULT_GENERIC_FAILURE; + + if (m_discoveryRunning) + { + stopDiscoveryTimer(); + + rc = BTRMGR_StopDeviceDiscovery(0, BTRMGR_DEVICE_OP_TYPE_AUDIO_OUTPUT); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to stop the discovery..!"); + } else { + LOGWARN ("Stopped discovery..!"); + } + + m_discoveryRunning = false; + } + + return BTRMGR_RESULT_SUCCESS == rc; + } + + void Bluetooth::startDiscoveryTimer(int msec) + { + stopDiscoveryTimer(); + _discoveryTimer.Schedule(Core::Time::Now().Add(msec), m_discoveryTimer); + } + + void Bluetooth::stopDiscoveryTimer() + { + _discoveryTimer.Revoke(m_discoveryTimer); + } + + void Bluetooth::onDiscoveryTimer() + { + stopDeviceDiscovery(); + } + + JsonArray Bluetooth::getDiscoveredDevices() + { + JsonArray deviceArray; + BTRMGR_DiscoveredDevicesList_t discoveredDevices; + + memset (&discoveredDevices, 0, sizeof(discoveredDevices)); + BTRMGR_Result_t rc = BTRMGR_GetDiscoveredDevices(0, &discoveredDevices); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get the discovered devices"); + } + else + { + int i = 0; + JsonObject deviceDetails; + LOGINFO ("Success.... Discovered %d Devices", discoveredDevices.m_numOfDevices); + for (; i < discoveredDevices.m_numOfDevices; i++) + { + deviceDetails["deviceID"] = std::to_string(discoveredDevices.m_deviceProperty[i].m_deviceHandle); + deviceDetails["name"] = string(discoveredDevices.m_deviceProperty[i].m_name); + deviceDetails["deviceType"] = string(BTRMGR_GetDeviceTypeAsString(discoveredDevices.m_deviceProperty[i].m_deviceType)); + deviceDetails["connected"] = discoveredDevices.m_deviceProperty[i].m_isConnected?true:false; + deviceDetails["paired"] = discoveredDevices.m_deviceProperty[i].m_isPairedDevice?true:false; + deviceArray.Add(deviceDetails); + } + } + return deviceArray; + } + + JsonArray Bluetooth::getPairedDevices() + { + JsonArray deviceArray; + BTRMGR_PairedDevicesList_t pairedDevices; + + memset (&pairedDevices, 0, sizeof(pairedDevices)); + BTRMGR_Result_t rc = BTRMGR_GetPairedDevices(0, &pairedDevices); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get the paired devices"); + } + else + { + int i = 0; + JsonObject deviceDetails; + LOGINFO ("Success.... Paired %d Devices", pairedDevices.m_numOfDevices); + for (; i < pairedDevices.m_numOfDevices; i++) + { + deviceDetails["deviceID"] = std::to_string(pairedDevices.m_deviceProperty[i].m_deviceHandle); + deviceDetails["name"] = string(pairedDevices.m_deviceProperty[i].m_name); + deviceDetails["deviceType"] = string(BTRMGR_GetDeviceTypeAsString(pairedDevices.m_deviceProperty[i].m_deviceType)); + deviceDetails["connected"] = pairedDevices.m_deviceProperty[i].m_isConnected?true:false; + deviceArray.Add(deviceDetails); + } + } + return deviceArray; + } + + JsonArray Bluetooth::getConnectedDevices() + { + JsonArray deviceArray; + BTRMGR_ConnectedDevicesList_t connectedDevices; + + memset (&connectedDevices, 0, sizeof(connectedDevices)); + BTRMGR_Result_t rc = BTRMGR_GetConnectedDevices(0, &connectedDevices); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get the connected devices"); + } + else + { + int i = 0; + JsonObject deviceDetails; + LOGINFO ("Success.... Connected %d Devices", connectedDevices.m_numOfDevices); + for (; i < connectedDevices.m_numOfDevices; i++) + { + deviceDetails["deviceID"] = std::to_string(connectedDevices.m_deviceProperty[i].m_deviceHandle); + deviceDetails["name"] = string(connectedDevices.m_deviceProperty[i].m_name); + deviceDetails["deviceType"] = string(BTRMGR_GetDeviceTypeAsString(connectedDevices.m_deviceProperty[i].m_deviceType)); + deviceDetails["activeState"] = std::to_string(connectedDevices.m_deviceProperty[i].m_powerStatus); + deviceArray.Add(deviceDetails); + } + } + return deviceArray; + } + + bool Bluetooth::setDeviceConnection(long long int deviceID, const string &enable, const string &deviceType) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMgrDeviceHandle deviceHandle = (BTRMgrDeviceHandle) deviceID; + + if (Utils::String::equal(deviceType, "LE TILE")) { + if (Utils::String::equal(enable, "DISCONNECT")) { + rc = BTRMGR_DisconnectFromDevice(0, deviceHandle); + } + else if (Utils::String::equal(enable, "CONNECT")) { + BTRMGR_DeviceOperationType_t stream_pref = BTRMGR_DEVICE_OP_TYPE_LE; + rc = BTRMGR_ConnectToDevice(0, deviceHandle, stream_pref); + } + } + else if (Utils::String::equal(deviceType, "HUMAN INTERFACE DEVICE")) { + if (Utils::String::equal(enable, "DISCONNECT")) { + rc = BTRMGR_DisconnectFromDevice(0, deviceHandle); + } + else if (Utils::String::equal(enable, "CONNECT")) { + BTRMGR_DeviceOperationType_t stream_pref = BTRMGR_DEVICE_OP_TYPE_HID; + rc = BTRMGR_ConnectToDevice(0, deviceHandle, stream_pref); + } + } + else if ((Utils::String::equal(deviceType, "SMARTPHONE")) || (Utils::String::equal(deviceType, "TABLET"))) { + if (Utils::String::equal(enable, "DISCONNECT")) { + rc = BTRMGR_StopAudioStreamingIn(0, deviceHandle); + } + else if (Utils::String::equal(enable, "CONNECT")) { + BTRMGR_DeviceOperationType_t stream_pref = BTRMGR_DEVICE_OP_TYPE_AUDIO_INPUT; + rc = BTRMGR_StartAudioStreamingIn(0, deviceHandle, stream_pref); + } + } + else { + if (Utils::String::equal(enable, "DISCONNECT")) { + rc = BTRMGR_StopAudioStreamingOut(0, deviceHandle); + } + else if (Utils::String::equal(enable, "CONNECT")) { + BTRMGR_DeviceOperationType_t stream_pref = BTRMGR_DEVICE_OP_TYPE_AUDIO_OUTPUT; + rc = BTRMGR_StartAudioStreamingOut(0, deviceHandle, stream_pref); + } + } + + if (BTRMGR_RESULT_SUCCESS != rc) + LOGERR("Failed to do setDeviceConnection"); + + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setAudioStream(long long int deviceID, const string &audioStreamName) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMGR_StreamOut_Type_t streamOutPref = BTRMGR_STREAM_PRIMARY; + if (Utils::String::equal(audioStreamName, "PRIMARY")) + { + streamOutPref = BTRMGR_STREAM_PRIMARY; + } else if (Utils::String::equal(audioStreamName, "AUXILIARY")) { + streamOutPref = BTRMGR_STREAM_AUXILIARY; + } + rc = BTRMGR_SetAudioStreamingOutType(0, streamOutPref); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to do setAudioStream"); + } + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setDevicePairing(long long int deviceID, bool pair) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMgrDeviceHandle deviceHandle = (BTRMgrDeviceHandle) deviceID; + if (pair) + { + rc = BTRMGR_PairDevice(0, deviceHandle); + } else{ + rc = BTRMGR_UnpairDevice(0, deviceHandle); + } + + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to do %s ", (pair ? "Pair" : "Unpair")); + } else { + LOGINFO("Successfully done %s ", (pair ? "Pair" : "Unpair")); + } + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setBluetoothEnabled(const string &enabled) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_GENERIC_FAILURE; + if (enabled == "BLUETOOTH_DISABLED") + { + rc = BTRMGR_SetAdapterPowerStatus (0, 0 /* FALSE */); + } + else if (enabled == "BLUETOOTH_ENABLED") + { + rc = BTRMGR_SetAdapterPowerStatus (0, 1 /* TRUE */); + } + else if (enabled == "BLUETOOTH_INPUT_ENABLED") + { + /* TODO: as the Audio IN is not supported yet */ + LOGERR("Bluetooth IN is not supported by STB"); + } + + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to do setBluetoothEnabled"); + } + + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setBluetoothDiscoverable(bool enabled, int timeout) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_GENERIC_FAILURE; + if (enabled) + { + rc = BTRMGR_SetAdapterDiscoverable(0, 1, timeout); + } + else + { + rc = BTRMGR_SetAdapterDiscoverable(0, 0, timeout); + } + + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to do setBluetoothDiscoverable"); + } + + return BTRMGR_RESULT_SUCCESS == rc; + } + + // Sets adapter name. No support for "power" yet + bool Bluetooth::setBluetoothProperties(const JsonObject& parameters) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + if (parameters.HasLabel("name")) { + string name; + getStringParameter("name", name); + LOGWARN ("Name received as %s", C_STR(name)); + rc = BTRMGR_SetAdapterName (0, C_STR(name)); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to set Name in setBluetoothProperties"); + } + else { + LOGINFO ("Successfully done setBluetoothProperties"); + } + } + return BTRMGR_RESULT_SUCCESS == rc; + } + + // Gets adapter name. No support for "power" yet + bool Bluetooth::getBluetoothProperties( JsonObject* rp) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + JsonObject response; // responding with a single object + + char adapterName[BTRMGR_NAME_LEN_MAX]; + rc = BTRMGR_GetAdapterName (0, &adapterName[0]); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get Name in getBluetoothProperties"); + } + else { + LOGINFO ("Successfully done getBluetoothProperties"); + } + + response["name"] = string(adapterName); + LOGWARN ("Name set as %s", adapterName); + if (rp) { + *rp = response; + } + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setAudioControlCommand(long long int deviceID, const string &audioCtrlCmd) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMgrDeviceHandle deviceHandle = (BTRMgrDeviceHandle) deviceID; + + if (audioCtrlCmd == CMD_AUDIO_CTRL_PLAY) { + BTRMGR_DeviceOperationType_t stream_pref = BTRMGR_DEVICE_OP_TYPE_AUDIO_INPUT; + rc = BTRMGR_StartAudioStreamingIn(0, deviceHandle, stream_pref); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_PAUSE) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_PAUSE); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_RESUME) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_PLAY); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_STOP) { + rc = BTRMGR_StopAudioStreamingIn(0, deviceHandle); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_SKIP_NEXT) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_NEXT); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_SKIP_PREV) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_PREVIOUS); + } //TODO + else if (audioCtrlCmd == CMD_AUDIO_CTRL_RESTART) { + rc = BTRMGR_RESULT_GENERIC_FAILURE; + /* could manipulate this action with skip track by setting Repeat - To confirm */ + } //TODO + else if (audioCtrlCmd == CMD_AUDIO_CTRL_MUTE) { + rc = BTRMGR_RESULT_GENERIC_FAILURE; + /* release data path and reacquire on unmute - to confirm */ + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_VOLUME_UP) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_VOLUMEUP); + } + else if (audioCtrlCmd == CMD_AUDIO_CTRL_VOLUME_DOWN) { + rc = BTRMGR_MediaControl (0, deviceHandle, BTRMGR_MEDIA_CTRL_VOLUMEDOWN); + } + + if (rc != BTRMGR_RESULT_SUCCESS) + { + LOGERR("Failed to do setAudioControlCommand"); + } else { + LOGINFO ("Successfully done setAudioControlCommand"); + } + + return BTRMGR_RESULT_SUCCESS == rc; + } + + bool Bluetooth::setEventResponse(long long int deviceID, const string &eventType, const string &respValue) + { + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMGR_EventResponse_t lstBtrMgrEvtRsp; + + memset(&lstBtrMgrEvtRsp, 0, sizeof(lstBtrMgrEvtRsp)); + + lstBtrMgrEvtRsp.m_deviceHandle = deviceID; + + if (eventType.compare(EVT_PAIRING_REQUEST)) { + lstBtrMgrEvtRsp.m_eventType = BTRMGR_EVENT_RECEIVED_EXTERNAL_PAIR_REQUEST; + lstBtrMgrEvtRsp.m_eventResp = Utils::String::equal(respValue, "ACCEPTED") ? 1 : 0; + } + else if (eventType.compare(EVT_CONNECTION_REQUEST)) { + lstBtrMgrEvtRsp.m_eventType = BTRMGR_EVENT_RECEIVED_EXTERNAL_CONNECT_REQUEST; + lstBtrMgrEvtRsp.m_eventResp = Utils::String::equal(respValue, "ACCEPTED") ? 1 : 0; + } + else if (eventType.compare(EVT_PLAYBACK_REQUEST)) { + lstBtrMgrEvtRsp.m_eventType = BTRMGR_EVENT_RECEIVED_EXTERNAL_PLAYBACK_REQUEST; + lstBtrMgrEvtRsp.m_eventResp = Utils::String::equal(respValue, "ACCEPTED") ? 1 : 0; + } + else { + lstBtrMgrEvtRsp.m_eventType = BTRMGR_EVENT_MAX; + } + + rc = BTRMGR_SetEventResponse(0, &lstBtrMgrEvtRsp); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to do setEventResponse"); + } else { + LOGINFO ("Successfully done setEventResponse"); + } + + return BTRMGR_RESULT_SUCCESS == rc; + } + + JsonObject Bluetooth::getDeviceInfo(long long int deviceID) + { + JsonObject deviceDetails; + string profileInfo; + + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMgrDeviceHandle deviceHandle = (BTRMgrDeviceHandle) deviceID; + string serviceInfo = ""; + BTRMGR_DevicesProperty_t deviceProperty; + memset (&deviceProperty, 0, sizeof(deviceProperty)); + + rc = BTRMGR_GetDeviceProperties(0, deviceHandle, &deviceProperty); + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get device details"); + } else { + deviceDetails["deviceID"] = std::to_string(deviceProperty.m_deviceHandle); + deviceDetails["name"] = string(deviceProperty.m_name); + deviceDetails["deviceType"] = string(BTRMGR_GetDeviceTypeAsString(deviceProperty.m_deviceType)); + deviceDetails["manufacturer"] = std::to_string(deviceProperty.m_vendorID); + deviceDetails["MAC"] = string(deviceProperty.m_deviceAddress); + deviceDetails["signalStrength"] = std::to_string(deviceProperty.m_signalLevel); + deviceDetails["rssi"] = std::to_string(deviceProperty.m_rssi); + for (int i = 0; i < deviceProperty.m_serviceInfo.m_numOfService; i++) + { + profileInfo += string(deviceProperty.m_serviceInfo.m_profileInfo[i].m_profile); + if ((i + 1) < deviceProperty.m_serviceInfo.m_numOfService) + { + profileInfo +=string(";"); + } + } + deviceDetails["supportedProfile"] = profileInfo; + } + return deviceDetails; + } + + JsonObject Bluetooth::getMediaTrackInfo(long long int deviceID) + { + JsonObject mediaTrackInfo; + BTRMGR_Result_t rc = BTRMGR_RESULT_SUCCESS; + BTRMgrDeviceHandle deviceHandle = (BTRMgrDeviceHandle) deviceID; + BTRMGR_MediaTrackInfo_t m_mediaTrackInfo; + + memset (&m_mediaTrackInfo, 0, sizeof(m_mediaTrackInfo)); + + rc = BTRMGR_GetMediaTrackInfo (0, deviceHandle, &m_mediaTrackInfo); + + if (BTRMGR_RESULT_SUCCESS != rc) + { + LOGERR("Failed to get Track details"); + } + else + { + mediaTrackInfo["album"] = string(m_mediaTrackInfo.pcAlbum); + mediaTrackInfo["genre"] = string(m_mediaTrackInfo.pcGenre); + mediaTrackInfo["title"] = string(m_mediaTrackInfo.pcTitle); + mediaTrackInfo["artist"] = string(m_mediaTrackInfo.pcArtist); + mediaTrackInfo["ui32Duration"] = std::to_string(m_mediaTrackInfo.ui32Duration); + mediaTrackInfo["ui32TrackNumber"] = std::to_string(m_mediaTrackInfo.ui32TrackNumber); + mediaTrackInfo["ui32NumberOfTracks"] = std::to_string(m_mediaTrackInfo.ui32NumberOfTracks); + } + return mediaTrackInfo; + } + + void Bluetooth::notifyEventWrapper (BTRMGR_EventMessage_t eventMsg) + { + JsonObject params; + string profileInfo; + string eventId; + LOGINFO ("Event notification: event of type %d received", eventMsg.m_eventType); + switch (eventMsg.m_eventType) { + case BTRMGR_EVENT_DEVICE_DISCOVERY_COMPLETE: + LOGINFO ("Received %s Event from BTRMgr", C_STR(STATUS_DISCOVERY_COMPLETED)); + params["newStatus"] = STATUS_DISCOVERY_COMPLETED; + eventId = EVT_STATUS_CHANGED; + + // TODO: Stopping the discovery timer and resetting the flag should not be needed on Discovery completed. + // But is it logical to expect DISCOVERY_COMPLETED, when Bluetooth Service has not asked BTRMgr to + // to Stop discovery. Should we change BTRMgr to send an alternate event to indicate DISCOVERY_PAUSED + // and DISCOVERY_RESUMED. + // Would it be sufficient to send the Discovery Type as part of DISCOVERY_STARTED and DISCOVERY_COMPLETE + // events from BTRMgr ?? + break; + + case BTRMGR_EVENT_DEVICE_PAIRING_COMPLETE: + LOGINFO ("Received %s Event from BTRMgr", C_STR(STATUS_PAIRING_CHANGE)); + params["newStatus"] = STATUS_PAIRING_CHANGE; + params["deviceID"] = C_STR(std::to_string(eventMsg.m_discoveredDevice.m_deviceHandle)); + params["name"] = string(eventMsg.m_discoveredDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_discoveredDevice.m_deviceType); + params["rawDeviceType"] = C_STR(std::to_string(eventMsg.m_discoveredDevice.m_ui32DevClassBtSpec)); + params["lastConnectedState"] = eventMsg.m_discoveredDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = eventMsg.m_discoveredDevice.m_isPairedDevice ? true : false; + params["connected"] = eventMsg.m_discoveredDevice.m_isConnected ? true : false; + + eventId = EVT_STATUS_CHANGED; + break; + + case BTRMGR_EVENT_DEVICE_UNPAIRING_COMPLETE: + LOGINFO ("Received %s Event from BTRMgr", C_STR(STATUS_PAIRING_CHANGE)); + params["newStatus"] = STATUS_PAIRING_CHANGE; + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = false; + params["connected"] = eventMsg.m_pairedDevice.m_isConnected ? true : false; + + eventId = EVT_STATUS_CHANGED; + break; + + case BTRMGR_EVENT_DEVICE_CONNECTION_COMPLETE: + case BTRMGR_EVENT_DEVICE_DISCONNECT_COMPLETE: /* Allow only AudioIn/Out & HID Connection Event propogation to XRE for now */ + if ((eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_WEARABLE_HEADSET) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HANDSFREE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_LOUDSPEAKER) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HEADPHONES) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_PORTABLE_AUDIO) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_CAR_AUDIO) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HIFI_AUDIO_DEVICE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_SMARTPHONE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_TABLET) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HID) ){ + + LOGINFO ("Received %s Event from BTRMgr", C_STR(STATUS_CONNECTION_CHANGE)); + params["newStatus"] = STATUS_CONNECTION_CHANGE; + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = true; + params["connected"] = eventMsg.m_pairedDevice.m_isConnected ? true : false; + + eventId = EVT_STATUS_CHANGED; + } + break; + + case BTRMGR_EVENT_DEVICE_DISCOVERY_STARTED: + LOGINFO ("Received %s Event from BTRMgr", C_STR(STATUS_DISCOVERY_STARTED)); + params["newStatus"] = STATUS_DISCOVERY_STARTED; + eventId = EVT_STATUS_CHANGED; + break; + + case BTRMGR_EVENT_RECEIVED_EXTERNAL_PAIR_REQUEST: + LOGINFO ("Received %s Event from BTRMgr", "external pairing request"); + params["deviceID"] = std::to_string(eventMsg.m_externalDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_externalDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_externalDevice.m_deviceType); + params["manufacturer"] = std::to_string(eventMsg.m_externalDevice.m_vendorID); + params["MAC"] = string(eventMsg.m_externalDevice.m_deviceAddress); + + for (int i = 0; i < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService; i++) { + profileInfo += string(eventMsg.m_externalDevice.m_serviceInfo.m_profileInfo[i].m_profile); + if ((i + 1) < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService) + profileInfo += string(";"); + } + + params["supportedProfile"] = profileInfo; + + if (eventMsg.m_externalDevice.m_externalDevicePIN == 0) { + params["pinRequired"] = "false"; + } + else { + params["pinRequired"] = "true"; + params["pinValue"] = std::to_string(eventMsg.m_externalDevice.m_externalDevicePIN); + } + + eventId = EVT_PAIRING_REQUEST; + break; + + case BTRMGR_EVENT_DEVICE_PAIRING_FAILED: + LOGERR("Received %s Event from BTRMgr", C_STR(STATUS_PAIRING_FAILED)); + params["newStatus"] = STATUS_PAIRING_FAILED; + params["deviceID"] = std::to_string(eventMsg.m_discoveredDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_discoveredDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_discoveredDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_discoveredDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_discoveredDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = eventMsg.m_discoveredDevice.m_isPairedDevice ? true : false; + params["connected"] = eventMsg.m_discoveredDevice.m_isConnected ? true : false; + + eventId = EVT_REQUEST_FAILED; + break; + + case BTRMGR_EVENT_DEVICE_UNPAIRING_FAILED: + LOGERR("Received %s Event from BTRMgr", C_STR(STATUS_PAIRING_FAILED)); + params["newStatus"] = STATUS_PAIRING_FAILED; + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = true; + params["connected"] = eventMsg.m_pairedDevice.m_isConnected ? true : false; + + eventId = EVT_REQUEST_FAILED; + break; + + case BTRMGR_EVENT_DEVICE_CONNECTION_FAILED: + case BTRMGR_EVENT_DEVICE_DISCONNECT_FAILED: /* Allow only AudioIn/Out & HID Connection Event propogation to XRE for now */ + if ((eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_WEARABLE_HEADSET) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HANDSFREE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_LOUDSPEAKER) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HEADPHONES) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_PORTABLE_AUDIO) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_CAR_AUDIO) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HIFI_AUDIO_DEVICE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_SMARTPHONE) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_TABLET) || + (eventMsg.m_pairedDevice.m_deviceType == BTRMGR_DEVICE_TYPE_HID) ){ + + LOGERR("Received %s Event from BTRMgr", C_STR(STATUS_CONNECTION_FAILED)); + params["newStatus"] = STATUS_CONNECTION_FAILED; + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice ? true : false; + params["paired"] = true; + params["connected"] = eventMsg.m_pairedDevice.m_isConnected ? true : false; + + eventId = EVT_REQUEST_FAILED; + } + break; + + case BTRMGR_EVENT_RECEIVED_EXTERNAL_CONNECT_REQUEST: + LOGERR("Received %s Event from BTRMgr", "external connection request"); + params["deviceID"] = std::to_string(eventMsg.m_externalDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_externalDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_externalDevice.m_deviceType); + params["manufacturer"] = std::to_string(eventMsg.m_externalDevice.m_vendorID); + params["MAC"] = string(eventMsg.m_externalDevice.m_deviceAddress); + + for (int i = 0; i < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService; i++) { + profileInfo += string(eventMsg.m_externalDevice.m_serviceInfo.m_profileInfo[i].m_profile); + if ((i + 1) < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService) + profileInfo += string(";"); + } + + params["supportedProfile"] = profileInfo; + + eventId = EVT_CONNECTION_REQUEST; + break; + + case BTRMGR_EVENT_RECEIVED_EXTERNAL_PLAYBACK_REQUEST: + LOGERR("Received %s Event from BTRMgr", "external playback request"); + params["deviceID"] = std::to_string(eventMsg.m_externalDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_externalDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_externalDevice.m_deviceType); + params["manufacturer"] = std::to_string(eventMsg.m_externalDevice.m_vendorID); + params["MAC"] = string(eventMsg.m_externalDevice.m_deviceAddress); + + for (int i = 0; i < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService; i++) { + profileInfo += string(eventMsg.m_externalDevice.m_serviceInfo.m_profileInfo[i].m_profile); + if ((i + 1) < eventMsg.m_externalDevice.m_serviceInfo.m_numOfService) + profileInfo += string(";"); + } + + params["supportedProfile"] = profileInfo; + + eventId = EVT_PLAYBACK_REQUEST; + break; + + case BTRMGR_EVENT_MEDIA_TRACK_STARTED: + LOGINFO ("Received %s Event from BTRMgr", C_STR(EVT_PLAYBACK_STARTED)); + params["action"] = std::string("started"); + params["deviceID"] = std::to_string(eventMsg.m_mediaInfo.m_deviceHandle); + params["position"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaPosition); + params["Duration"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaDuration); + + eventId = EVT_PLAYBACK_STARTED; + break; + case BTRMGR_EVENT_MEDIA_TRACK_PAUSED: + LOGINFO ("Received %s Event from BTRMgr", C_STR(EVT_PLAYBACK_PAUSED)); + params["action"] = std::string("paused"); + params["deviceID"] = std::to_string(eventMsg.m_mediaInfo.m_deviceHandle); + params["position"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaPosition); + params["Duration"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaDuration); + + eventId =EVT_PLAYBACK_PAUSED; + break; + + case BTRMGR_EVENT_MEDIA_TRACK_STOPPED: + LOGINFO ("Received %s Event from BTRMgr", C_STR(EVT_PLAYBACK_STOPPED)); + params["action"] = std::string("stopped"); + params["deviceID"] = C_STR(std::to_string(eventMsg.m_mediaInfo.m_deviceHandle)); + params["position"] = C_STR(std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaPosition)); + params["Duration"] = C_STR(std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaDuration)); + + eventId = EVT_PLAYBACK_STOPPED; + break; + + case BTRMGR_EVENT_MEDIA_PLAYBACK_ENDED: + LOGINFO ("Received %s Event from BTRMgr", C_STR(EVT_PLAYBACK_ENDED)); + params["action"] = std::string("ended"); + params["deviceID"] = std::to_string(eventMsg.m_mediaInfo.m_deviceHandle); + + eventId = EVT_PLAYBACK_ENDED; + break; + + case BTRMGR_EVENT_MEDIA_TRACK_PLAYING: + case BTRMGR_EVENT_MEDIA_TRACK_POSITION: + LOGINFO ("Received Playback Position Event from BTRMgr"); + params["deviceID"] = std::to_string(eventMsg.m_mediaInfo.m_deviceHandle); + params["position"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaPosition); + params["Duration"] = std::to_string(eventMsg.m_mediaInfo.m_mediaPositionInfo.m_mediaDuration); + + eventId = EVT_PLAYBACK_POSITION; + break; + + case BTRMGR_EVENT_MEDIA_TRACK_CHANGED: + params["deviceID"] = std::to_string(eventMsg.m_mediaInfo.m_deviceHandle); + params["album"] = string(eventMsg.m_mediaInfo.m_mediaTrackInfo.pcAlbum); + params["genre"] = string(eventMsg.m_mediaInfo.m_mediaTrackInfo.pcGenre); + params["title"] = string(eventMsg.m_mediaInfo.m_mediaTrackInfo.pcTitle); + params["artist"] = string(eventMsg.m_mediaInfo.m_mediaTrackInfo.pcArtist); + params["ui32Duration"] = std::to_string(eventMsg.m_mediaInfo.m_mediaTrackInfo.ui32Duration); + params["ui32TrackNumber"] = std::to_string(eventMsg.m_mediaInfo.m_mediaTrackInfo.ui32TrackNumber); + params["ui32NumberOfTracks"] = std::to_string(eventMsg.m_mediaInfo.m_mediaTrackInfo.ui32NumberOfTracks); + + eventId = EVT_PLAYBACK_NEW_TRACK; + break; + + case BTRMGR_EVENT_DEVICE_FOUND: + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice?true:false; + + eventId = EVT_DEVICE_FOUND; + break; + + case BTRMGR_EVENT_DEVICE_OUT_OF_RANGE: + params["deviceID"] = std::to_string(eventMsg.m_pairedDevice.m_deviceHandle); + params["name"] = string(eventMsg.m_pairedDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_pairedDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_pairedDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_pairedDevice.m_isLastConnectedDevice?true:false; + eventId = EVT_DEVICE_LOST_OR_OUT_OF_RANGE; + break; + + case BTRMGR_EVENT_DEVICE_DISCOVERY_UPDATE: + params["deviceID"] = std::to_string(eventMsg.m_discoveredDevice.m_deviceHandle); + params["discoveryType"] = eventMsg.m_discoveredDevice.m_isDiscovered ? "DISCOVERED":"LOST"; + params["name"] = string(eventMsg.m_discoveredDevice.m_name); + params["deviceType"] = BTRMGR_GetDeviceTypeAsString(eventMsg.m_discoveredDevice.m_deviceType); + params["rawDeviceType"] = std::to_string(eventMsg.m_discoveredDevice.m_ui32DevClassBtSpec); + params["lastConnectedState"] = eventMsg.m_discoveredDevice.m_isLastConnectedDevice? true:false; + params["paired"] = eventMsg.m_discoveredDevice.m_isPairedDevice ? true:false; + + eventId = EVT_DEVICE_DISCOVERY_UPDATE; + break; + + // TODO: implement or delete these values from enum + case BTRMGR_EVENT_MAX: + break; + case BTRMGR_EVENT_DEVICE_OP_READY: + break; + case BTRMGR_EVENT_DEVICE_OP_INFORMATION: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_NAME: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_VOLUME: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_EQUALIZER_OFF: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_EQUALIZER_ON: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_SHUFFLE_OFF: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_SHUFFLE_ALLTRACKS: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_SHUFFLE_GROUP: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_REPEAT_OFF: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_REPEAT_SINGLETRACK: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_REPEAT_ALLTRACKS: + break; + case BTRMGR_EVENT_MEDIA_PLAYER_REPEAT_GROUP: + break; + case BTRMGR_EVENT_MEDIA_ALBUM_INFO: + break; + case BTRMGR_EVENT_MEDIA_ARTIST_INFO: + break; + case BTRMGR_EVENT_MEDIA_GENRE_INFO: + break; + case BTRMGR_EVENT_MEDIA_COMPILATION_INFO: + break; + case BTRMGR_EVENT_MEDIA_PLAYLIST_INFO: + break; + case BTRMGR_EVENT_MEDIA_TRACKLIST_INFO: + break; + } + + if (!eventId.empty()) + { + sendNotify(C_STR(eventId), params); + } + return; + } + // + /// Internal methods end + + /// Registered methods begin + // + uint32_t Bluetooth::getApiVersionNumber(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + response["version"] = m_apiVersionNumber; + returnResponse(true); + } + + uint32_t Bluetooth::startScanWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + int timeout = -1; + string profile; + bool timeoutDefined = false; + bool profileDefined = false; + bool successFlag; + if (parameters.HasLabel("timeout")) + { + getNumberParameter("timeout", timeout); + timeoutDefined = true; + } + + if (parameters.HasLabel("profile")) + { + getStringParameter("profile", profile); + profileDefined = true; + } + if (timeoutDefined && profileDefined) + { + LOGINFO("Making a call with timeout=%d sec profile=%s", timeout, profile.c_str()); + response["status"] = startDeviceDiscovery(timeout, profile); + successFlag = true; + } else if (timeoutDefined) { + LOGINFO("Making a call with timeout=%d sec", timeout); + response["status"] = startDeviceDiscovery(timeout); + successFlag = true; + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"timeout\": \"5\", \"profile\": \"SMARTPHONE\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::stopScanWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + stopDeviceDiscovery(); + returnResponse(true); + } + + uint32_t Bluetooth::isDiscoverableWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + response["discoverable"] = isAdapterDiscoverable(); + returnResponse(true); + } + + uint32_t Bluetooth::setDiscoverableWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + bool discoverable = false; + int timeout; + + if (parameters.HasLabel("timeout")) + { + getNumberParameter("timeout", timeout); + } else { + timeout = -1; + } + + if (parameters.HasLabel("discoverable")) { + getBoolParameter("discoverable", discoverable); + LOGINFO("Making a call with discoverable: %s timeout=%d", discoverable ? "YES" : "NO", timeout); + successFlag = setBluetoothDiscoverable(discoverable, timeout); + } else { + LOGERR("Please specify parameters. Example (timeout is optional): \"params\": {\"discoverable\": true, \"timeout\": \"10\"}"); + successFlag = false; + } + + returnResponse(successFlag); + } + + uint32_t Bluetooth::getDiscoveredDevicesWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + response["discoveredDevices"] = getDiscoveredDevices(); + returnResponse(true); + } + + uint32_t Bluetooth::getPairedDevicesWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + response["pairedDevices"] = getPairedDevices(); + returnResponse(true); + } + + uint32_t Bluetooth::getConnectedDevicesWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + UNUSED(parameters); + response["connectedDevices"] = getConnectedDevices(); + returnResponse(true); + } + + uint32_t Bluetooth::connectWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + string enable = "CONNECT"; // "CONNECT" or "DISCONNECT" + string deviceType; + bool deviceTypeDefined = false; + bool successFlag; + + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if (parameters.HasLabel("deviceType")) + { + getStringParameter("deviceType", deviceType); + deviceTypeDefined = true; + } else { + deviceType = "SMARTPHONE"; + } + + if (deviceIDDefined && deviceTypeDefined) + { + LOGINFO("Making a call with deviceID=%llu enable=%s deviceType=%s", deviceID, enable.c_str(), deviceType.c_str()); + successFlag = setDeviceConnection(deviceID, enable, deviceType); + } else if (deviceIDDefined) { + LOGINFO("Making a call with deviceID=%llu enable=%s", deviceID, enable.c_str()); + successFlag = setDeviceConnection(deviceID, enable); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::disconnectWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + string enable = "DISCONNECT"; // "CONNECT" or "DISCONNECT" + string deviceType; + bool deviceTypeDefined = false; + bool successFlag; + + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if (parameters.HasLabel("deviceType")) + { + getStringParameter("deviceType", deviceType); + deviceTypeDefined = true; + } else { + deviceType = "SMARTPHONE"; + } + + if (deviceIDDefined && deviceTypeDefined) + { + LOGINFO("Making a call with deviceID=%llu enable=%s deviceType=%s", deviceID, enable.c_str(), deviceType.c_str()); + successFlag = setDeviceConnection(deviceID, enable, deviceType); + } else if (deviceIDDefined) { + LOGINFO("Making a call with deviceID=%llu enable=%s", deviceID, enable.c_str()); + successFlag = setDeviceConnection(deviceID, enable); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::setAudioStreamWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + string audioStreamName; //"PRIMARY" or "AUXILIARY" + bool audioStreamNameDefined = false; + bool successFlag; + + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if (parameters.HasLabel("audioStreamName")) + { + getStringParameter("audioStreamName", audioStreamName); + audioStreamNameDefined = true; + } + if (deviceIDDefined && audioStreamNameDefined) + { + LOGINFO("Making a call with deviceID=%llu audioStreamName=%s", deviceID, audioStreamName.c_str()); + successFlag = setAudioStream(deviceID, audioStreamName); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\", \"audioStreamName\": \"PRIMARY\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::pairWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + bool pair = true; + + if (parameters.HasLabel("deviceID")) { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if(deviceIDDefined) + { + LOGINFO("Making a call with deviceID=%llu pair=%s", deviceID, pair?"true":"false"); + successFlag = setDevicePairing(deviceID, pair); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::unpairWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + bool pair = false; + + if (parameters.HasLabel("deviceID")) { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if(deviceIDDefined) + { + LOGINFO("Making a call with deviceID=%llu pair=%s", deviceID, pair?"true":"false"); + successFlag = setDevicePairing(deviceID, pair); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::enableWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + string enabled = ENABLE_BLUETOOTH_ENABLED; + successFlag = setBluetoothEnabled(enabled); + returnResponse(successFlag); + } + + uint32_t Bluetooth::disableWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + string enabled = ENABLE_BLUETOOTH_DISABLED; + successFlag = setBluetoothEnabled(enabled); + returnResponse(successFlag); + } + + uint32_t Bluetooth::getNameWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + successFlag = getBluetoothProperties(&response); + returnResponse(successFlag); + } + + uint32_t Bluetooth::setNameWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + bool successFlag; + successFlag = setBluetoothProperties(parameters); + returnResponse(successFlag); + } + + uint32_t Bluetooth::sendAudioPlaybackCommandWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + string audioCtrlCmd; // see CMD_AUDIO_CTRL_ entries + bool audioCtrlCmdDefined = false; + bool successFlag; + + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if (parameters.HasLabel("command")) + { + getStringParameter("command", audioCtrlCmd); + audioCtrlCmdDefined = true; + } + + if (deviceIDDefined && audioCtrlCmdDefined) + { + LOGINFO("Making a call with deviceID=%llu audioCtrlCmd=%s", deviceID, audioCtrlCmd.c_str()); + successFlag = setAudioControlCommand(deviceID, audioCtrlCmd); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\", \"command\": \"PLAY\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::setEventResponseWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool deviceIDDefined = false; + string eventType; // see EVT_ definitions, e.g. EVT_PAIRING_REQUEST + bool eventTypeDefined = false; + bool successFlag; + string responseValue; + bool responseValueDefined = false; + + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + deviceIDDefined = true; + } + + if (parameters.HasLabel("eventType")) + { + getStringParameter("eventType", eventType); + eventTypeDefined = true; + } + + if (parameters.HasLabel("responseValue")) + { + getStringParameter("responseValue", responseValue); + responseValueDefined = true; + } + + if(deviceIDDefined && responseValueDefined && eventTypeDefined) + { + LOGINFO("Making a call with deviceID=%llu eventType=%s responseValue=%s", deviceID, C_STR(eventType), C_STR(responseValue)); + successFlag = setEventResponse(deviceID, eventType, responseValue); + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\", \"eventType\": \"pairingRequest\", \"responseValue\": \"ACCEPTED\"}"); + successFlag = false; + } + + returnResponse(successFlag); + } + + uint32_t Bluetooth::getDeviceInfoWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool successFlag; + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + response["deviceInfo"] = getDeviceInfo(deviceID); + successFlag = true; + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + + uint32_t Bluetooth::getMediaTrackInfoWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFO(); + string deviceIDStr; + long long int deviceID = 0; + bool successFlag; + if (parameters.HasLabel("deviceID")) + { + getStringParameter("deviceID", deviceIDStr); + deviceID = stoll(deviceIDStr); + response["trackInfo"] = getMediaTrackInfo(deviceID); + successFlag = true; + } else { + LOGERR("Please specify parameters. Example: \"params\": {\"deviceID\": \"271731989589742\"}"); + successFlag = false; + } + returnResponse(successFlag); + } + // + /// Registered methods end + + uint64_t DiscoveryTimer::Timed(const uint64_t scheduledTime) + { + uint64_t result = 0; + m_bt->onDiscoveryTimer(); + return(result); + } + } // Plugin +} // WPEFramework diff --git a/Bluetooth/Bluetooth.h b/Bluetooth/Bluetooth.h new file mode 100644 index 0000000000..b1559be7ea --- /dev/null +++ b/Bluetooth/Bluetooth.h @@ -0,0 +1,217 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include + +#include "Module.h" +#include "utils.h" +#include "AbstractPlugin.h" + +#include "btmgr.h" //TODO: can we move it to the module? Required by notifyEventWrapper() + +namespace WPEFramework { + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class Bluetooth; + class DiscoveryTimer + { + private: + DiscoveryTimer() = delete; + DiscoveryTimer& operator=(const DiscoveryTimer& RHS) = delete; + + public: + DiscoveryTimer(Bluetooth* bt): m_bt(bt){} + DiscoveryTimer(const DiscoveryTimer& copy): m_bt(copy.m_bt){} + ~DiscoveryTimer() {} + + inline bool operator==(const DiscoveryTimer& RHS) const + { + return(m_bt == RHS.m_bt); + } + + public: + uint64_t Timed(const uint64_t scheduledTime); + + private: + Bluetooth* m_bt; + }; + + class Bluetooth : public AbstractPlugin { + private: + + // We do not allow this plugin to be copied !! + Bluetooth(const Bluetooth&) = delete; + Bluetooth& operator=(const Bluetooth&) = delete; + + // Registered methods begin + // Note: `JsonObject& parameters` corresponds to `params` in JSON RPC call + uint32_t getApiVersionNumber(const JsonObject& parameters, JsonObject& response); + uint32_t startScanWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t stopScanWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t isDiscoverableWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setDiscoverableWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getDiscoveredDevicesWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getPairedDevicesWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getConnectedDevicesWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t connectWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t disconnectWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setAudioStreamWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t pairWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t unpairWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t enableWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t disableWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getNameWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setNameWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t sendAudioPlaybackCommandWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setEventResponseWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getDeviceInfoWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getMediaTrackInfoWrapper(const JsonObject& parameters, JsonObject& response); + // Registered methods end + + private: /*internal methods*/ + void getStatusSupport(string& status); + bool isAdapterDiscoverable(); + string startDeviceDiscovery(int timeout, const string &discProfile = "LOUDSPEAKER, HEADPHONES, WEARABLE HEADSET, HIFI AUDIO DEVICE"); + bool stopDeviceDiscovery(); + void startDiscoveryTimer(int msec); + void stopDiscoveryTimer(); + void onDiscoveryTimer(); + JsonArray getDiscoveredDevices(); + JsonArray getPairedDevices(); + JsonArray getConnectedDevices(); + bool setDeviceConnection(long long int deviceID, const string &enable, const string &deviceType = "UNKNOWN DEVICE"); + bool setAudioStream(long long int deviceID, const string &audioStreamName); + bool setDevicePairing(long long int deviceID, bool pair); + bool setBluetoothEnabled(const string &enabled); + bool setBluetoothDiscoverable(bool enabled, int timeout); + bool getBluetoothProperties(JsonObject* rp); + bool setBluetoothProperties(const JsonObject& properties); + bool setAudioControlCommand(long long int deviceID, const string &audioCtrlCmd); + bool setEventResponse(long long int deviceID, const string &eventType, const string &respValue); + JsonObject getDeviceInfo(long long int deviceID); + JsonObject getMediaTrackInfo(long long int deviceID); + public: + static const short API_VERSION_NUMBER_MAJOR; + static const short API_VERSION_NUMBER_MINOR; + static const string SERVICE_NAME; + static const string METHOD_START_SCAN; + static const string METHOD_STOP_SCAN; + static const string METHOD_IS_DISCOVERABLE; + static const string METHOD_GET_DISCOVERED_DEVICES; + static const string METHOD_GET_PAIRED_DEVICES; + static const string METHOD_GET_CONNECTED_DEVICES; + static const string METHOD_CONNECT; + static const string METHOD_DISCONNECT; + static const string METHOD_SET_AUDIO_STREAM; + static const string METHOD_PAIR; + static const string METHOD_UNPAIR; + static const string METHOD_ENABLE; + static const string METHOD_DISABLE; + static const string METHOD_SET_DISCOVERABLE; + static const string METHOD_GET_NAME; + static const string METHOD_SET_NAME; + static const string METHOD_SET_AUDIO_PLAYBACK_COMMAND; + static const string METHOD_SET_EVENT_RESPONSE; + static const string METHOD_GET_DEVICE_INFO; + static const string METHOD_GET_AUDIO_INFO; + static const string METHOD_GET_API_VERSION_NUMBER; + static const string EVT_STATUS_CHANGED; + static const string EVT_PAIRING_REQUEST; + static const string EVT_REQUEST_FAILED; + static const string EVT_CONNECTION_REQUEST; + + static const string EVT_PLAYBACK_REQUEST; + static const string EVT_PLAYBACK_STARTED; + static const string EVT_PLAYBACK_PAUSED; + static const string EVT_PLAYBACK_RESUMED; + static const string EVT_PLAYBACK_STOPPED; + static const string EVT_PLAYBACK_ENDED; + + static const string EVT_PLAYBACK_POSITION; + static const string EVT_PLAYBACK_NEW_TRACK; + static const string EVT_DEVICE_FOUND; + static const string EVT_DEVICE_LOST_OR_OUT_OF_RANGE; + static const string EVT_DEVICE_DISCOVERY_UPDATE; + + Bluetooth(); + virtual ~Bluetooth(); + virtual string Information() const override; + + public: + static Bluetooth* _instance; + void notifyEventWrapper (BTRMGR_EventMessage_t eventMsg); + + private: + static const string STATUS_NO_BLUETOOTH_HARDWARE; + static const string STATUS_SOFTWARE_DISABLED; + static const string STATUS_AVAILABLE; + static const string ENABLE_CONNECT; + static const string ENABLE_DISCONNECT; + static const string ENABLE_BLUETOOTH_ENABLED; + static const string ENABLE_BLUETOOTH_DISABLED; + static const string ENABLE_BLUETOOTH_INPUT_ENABLED; + + static const string ENABLE_PRIMARY_AUDIO; + static const string ENABLE_AUXILIARY_AUDIO; + static const string STATUS_HARDWARE_AVAILABLE; + static const string STATUS_HARDWARE_DISABLED; + static const string STATUS_SOFTWARE_ENABLED; + static const string STATUS_SOFTWARE_INPUT_ENABLED; + static const string STATUS_PAIRING_CHANGE; + static const string STATUS_CONNECTION_CHANGE; + static const string STATUS_DISCOVERY_STARTED; + static const string STATUS_DISCOVERY_COMPLETED; + static const string STATUS_PAIRING_FAILED; + static const string STATUS_CONNECTION_FAILED; + + static const string CMD_AUDIO_CTRL_PLAY; + static const string CMD_AUDIO_CTRL_STOP; + static const string CMD_AUDIO_CTRL_PAUSE; + static const string CMD_AUDIO_CTRL_RESUME; + static const string CMD_AUDIO_CTRL_SKIP_NEXT; + static const string CMD_AUDIO_CTRL_SKIP_PREV; + static const string CMD_AUDIO_CTRL_RESTART; + static const string CMD_AUDIO_CTRL_VOLUME_UP; + static const string CMD_AUDIO_CTRL_VOLUME_DOWN; + static const string CMD_AUDIO_CTRL_MUTE; + + uint32_t m_apiVersionNumber; + // Assuming that there will be only one threaded call at a time (which is the case for Bluetooth) + // Otherwise we might need a thread for each async command for better performance + std::thread m_executionThread; + bool m_discoveryRunning; + DiscoveryTimer m_discoveryTimer; + friend class DiscoveryTimer; + }; + } // Plugin +} // WPEFramework diff --git a/Bluetooth/CMakeLists.txt b/Bluetooth/CMakeLists.txt new file mode 100644 index 0000000000..5ee9dde4ad --- /dev/null +++ b/Bluetooth/CMakeLists.txt @@ -0,0 +1,51 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME Bluetooth) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + Bluetooth.cpp + Module.cpp +) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") + + +find_package(BTMGR) +if(BTMGR_FOUND) + message("Found BTMGR") + target_include_directories(${MODULE_NAME} PRIVATE ${BTMGR_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${BTMGR_LIBRARIES}) +else (BTMGR_FOUND) + message(SEND_ERROR "BTMGR not found") +endif(BTMGR_FOUND) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) +target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/Bluetooth/Module.cpp b/Bluetooth/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/Bluetooth/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Bluetooth/Module.h b/Bluetooth/Module.h new file mode 100644 index 0000000000..44ee9ef394 --- /dev/null +++ b/Bluetooth/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME Bluetooth +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/Bluetooth/README.md b/Bluetooth/README.md new file mode 100644 index 0000000000..b605e89e84 --- /dev/null +++ b/Bluetooth/README.md @@ -0,0 +1,113 @@ +# BluetoothSettings + +## Versions +`org.rdk.Bluetooth.1` + +## Methods: +``` +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.enable"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.disable"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getName"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.setName", "params":{"name": "Xfinity Bluetooth Audio"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.isDiscoverable"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.setDiscoverable", "params":{"discoverable":true, "timeout":10}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.startScan", "params": {"timeout": "5", "profile": "SMARTPHONE"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.stopScan"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getDiscoveredDevices"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getConnectedDevices"}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.pair", "params": {"deviceID": "256168644324480"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.unpair", "params": {"deviceID": "256168644324480"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.connect", "params": {"deviceID": "256168644324480", "deviceType": "SMARTPHONE", "profile": "SMARTPHONE"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.disconnect", "params": {"deviceID": "256168644324480"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.setAudioStream", "params": {"deviceID": "256168644324480", "audioStreamName": "PRIMARY"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getDeviceInfo", "params":{"deviceID":"256168644324480"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.getAudioInfo", "params": {"deviceID": "256168644324480"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0", "id":"3", "method":"org.rdk.Bluetooth.1.sendAudioPlaybackCommand", "params": {"deviceID": "256168644324480", "command": "PLAY"}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id": "3", "method":"org.rdk.Bluetooth.1.respondToEvent", "params": {"deviceID": "256168644324480", "eventType": "onPairingRequest", "responseValue": "ACCEPTED"}}' http://127.0.0.1:9998/jsonrpc +``` + +## Responses: +``` +getApiVersionNumber: +{"jsonrpc":"2.0","id":3,"result":{"version":1,"success":true}} + +enable: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +disable: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +getName: +{"jsonrpc":"2.0","id":3,"result":{"name":"Xfinity Bluetooth Audio","success":true}} + +setName: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +isDiscoverable: +{"jsonrpc":"2.0","id":3,"result":{"discoverable":false,"success":true}} + +setDiscoverable: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +startScan: +{"jsonrpc":"2.0","id":3,"result":{"status":"AVAILABLE","success":true}} + +stopScan: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +getDiscoveredDevices: +{"jsonrpc":"2.0","id":3,"result":{"discoveredDevices":[{"deviceID":"61579454946360","name":"[TV] UE32J5530","deviceType":"TV","connected":false,"paired":false}],"success":true}} + +getPairedDevices: +{"jsonrpc":"2.0","id":3,"result":{"pairedDevices":[{"deviceID":"256168644324480","name":"Eleven","deviceType":"SMARTPHONE","connected":true},{"deviceID":"26499258260618","name":"Little Big","deviceType":"SMARTPHONE","connected":false}],"success":true}} + +getConnectedDevices: +{"jsonrpc":"2.0","id":3,"result":{"connectedDevices":[{"deviceID":"256168644324480","name":"Eleven","deviceType":"SMARTPHONE","activeState":"0"}],"success":true}} + +pair: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +unpair: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +connect: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +disconnect: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +setAudioStream: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +getDeviceInfo: +{"jsonrpc":"2.0","id":3,"result":{"deviceInfo":{"deviceID":"256168644324480","name":"Eleven","deviceType":"SMARTPHONE","manufacturer":"640","MAC":"E8:FB:E9:0C:2C:80","signalStrength":"0","rssi":"0","supportedProfile":"Not Identified;Not Identified;Audio Source;AV Remote Target;AV Remote;Not Identified;Handsfree - Audio Gateway;Not Identified;Not Identified;PnP Information;Generic Attribute;Not Identified"},"success":true}} + +getAudioInfo: +{"jsonrpc":"2.0","id":3,"result":{"trackInfo":{"album":"Spacebound Apes","genre":"Jazz","title":"Grace","artist":"Neil Cowley Trio","ui32Duration":"217292","ui32TrackNumber":"1","ui32NumberOfTracks":"73"},"success":true}} + +sendAudioPlaybackCommand: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} + +respondToEvent: +{"jsonrpc":"2.0","id":3,"result":{"success":true}} +``` + +## Events +``` +onStatusChanged +onPairingRequest +onRequestFailed +onConnectionRequest +onPlaybackRequest +onPlaybackChange +onPlaybackProgress +onPlaybackNewTrack +onDeviceFound +onDeviceLost +onDiscoveredDevice +``` + +## Full Reference +https://etwiki.sys.comcast.net/display/RDKV/Bluetooth + diff --git a/Bluetooth/cmake/FindBTMGR.cmake b/Bluetooth/cmake/FindBTMGR.cmake new file mode 100644 index 0000000000..438accfb57 --- /dev/null +++ b/Bluetooth/cmake/FindBTMGR.cmake @@ -0,0 +1,44 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +# - Try to find Bluetooth Manager Library +# Once done this will define +# BTMGR_FOUND - System has DS +# BTMGR_INCLUDE_DIRS - The DS include directories +# BTMGR_LIBRARIES - The libraries needed to use DS +# BTMGR_FLAGS - The flags needed to use DS +# + +find_package(PkgConfig) + +find_library(BTMGR_LIBRARIES NAMES BTMgr) +find_path(BTMGR_INCLUDE_DIRS NAMES btmgr.h PATH_SUFFIXES rdk/btmgr) + +#set(BTMGR_LIBRARIES ${BTMGR_LIBRARIES} +set(BTMGR_LIBRARIES ${BTMGR_LIBRARIES} CACHE PATH "Path to BTMGR library") +#set(BTMGR_INCLUDE_DIRS ${BTMGR_INCLUDE_DIRS} +set(BTMGR_INCLUDE_DIRS ${BTMGR_INCLUDE_DIRS} CACHE PATH "Path to BTMGR include") + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(BTMGR DEFAULT_MSG DS_INCLUDE_DIRS DS_LIBRARIES) + +mark_as_advanced( + BTMGR_FOUND + BTMGR_INCLUDE_DIRS + BTMGR_LIBRARIES + BTMGR_LIBRARY_DIRS + BTMGR_FLAGS) diff --git a/DataCapture/CMakeLists.txt b/DataCapture/CMakeLists.txt new file mode 100644 index 0000000000..acdeb10ed7 --- /dev/null +++ b/DataCapture/CMakeLists.txt @@ -0,0 +1,62 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME DataCapture) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + socket_adaptor.cpp + DataCapture.cpp + Module.cpp + ../helpers/utils.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +find_package(Curl) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +find_package(AC) +if (AC_FOUND) + find_package(IARMBus) + add_definitions(-DAC_FOUND) + target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) + target_include_directories(${MODULE_NAME} PRIVATE ${AC_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} + PRIVATE + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${CURL_LIBRARY} + ${IARMBUS_LIBRARIES} + ${AC_LIBRARIES} + ) + +else(AC_FOUND) + target_link_libraries(${MODULE_NAME} + PRIVATE + ${NAMESPACE}Plugins::${NAMESPACE}Plugins + ${CURL_LIBRARY} + ) +endif(AC_FOUND) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/DataCapture/DataCapture.config b/DataCapture/DataCapture.config new file mode 100644 index 0000000000..388f1d63ee --- /dev/null +++ b/DataCapture/DataCapture.config @@ -0,0 +1,3 @@ +set (autostart false) +set (preconditions Platform) +set (callsign org.rdk.dataCapture) \ No newline at end of file diff --git a/DataCapture/DataCapture.cpp b/DataCapture/DataCapture.cpp new file mode 100644 index 0000000000..9c7280dc7f --- /dev/null +++ b/DataCapture/DataCapture.cpp @@ -0,0 +1,538 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 +#include +#include "audiocapturemgr_iarm.h" +#undef LOG // we don't need LOG from audiocapturemgr_iarm as we are defining our own LOG +#include "DataCapture.h" +#include +#include "socket_adaptor.h" + +const string WPEFramework::Plugin::DataCapture::SERVICE_NAME = "org.rdk.DataCapture"; +const string WPEFramework::Plugin::DataCapture::METHOD_ENABLE_AUDIO_CAPTURE = "enableAudioCapture"; +const string WPEFramework::Plugin::DataCapture::METHOD_GET_AUDIO_CLIP = "getAudioClip"; +const string WPEFramework::Plugin::DataCapture::EVT_ON_AUDIO_CLIP_READY = "onAudioClipReady"; +pthread_mutex_t WPEFramework::Plugin::DataCapture::_mutex = PTHREAD_MUTEX_INITIALIZER; + +using namespace std; +using namespace audiocapturemgr; + +namespace WPEFramework { + + namespace Plugin { + + static bool verify_result(IARM_Result_t ret, iarmbus_acm_arg_t ¶m) + { + if(IARM_RESULT_SUCCESS != ret) + { + LOGERR("Bus call failed."); + return false; + } + if(0 != param.result) + { + LOGERR("ACM implementation of the bus call returned failure."); + return false; + } + return true; + } + + static void cleanup_samples() + { + string path(AUDIOCAPTUREMGR_FILE_PATH AUDIOCAPTUREMGR_FILENAME_PREFIX "*"); + LOGINFO("by path %s", C_STR(path)); + + Core::Directory dir(C_STR(path)); + while (dir.Next()) Core::File(AUDIOCAPTUREMGR_FILE_PATH + dir.Name()).Destroy(); + } + + SERVICE_REGISTRATION(DataCapture, 1, 0); + + DataCapture* DataCapture::_instance = nullptr; + + DataCapture::DataCapture() + : AbstractPlugin() + , _session_id(-1) + , _max_supported_duration(0) + , _is_precapture(false) + , _duration(0) + { + LOGINFO("ctor"); + + cleanup_samples(); + + DataCapture::_instance = this; + Register(METHOD_ENABLE_AUDIO_CAPTURE, &DataCapture::enableAudioCaptureWrapper, this); + Register(METHOD_GET_AUDIO_CLIP, &DataCapture::getAudioClipWrapper, this); + + _sock_adaptor = new socket_adaptor(); + } + + DataCapture::~DataCapture() + { + LOGINFO("dtor"); + delete _sock_adaptor; + DataCapture::_instance = nullptr; + } + + const string DataCapture::Initialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + InitializeIARM(); + return ""; + } + + void DataCapture::Deinitialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + DeinitializeIARM(); + } + + string DataCapture::Information() const + { + return(string("{\"service\": \"") + SERVICE_NAME + string("\"}")); + } + + void DataCapture::InitializeIARM() + { + LOGINFO(); + + if (Utils::IARM::init()) + { + IARM_Result_t res; + IARM_CHECK( IARM_Bus_RegisterEventHandler(IARMBUS_AUDIOCAPTUREMGR_NAME, DATA_CAPTURE_IARM_EVENT_AUDIO_CLIP_READY, iarmEventHandler)); + } + } + + void DataCapture::DeinitializeIARM() + { + LOGINFO(); + + if (Utils::IARM::isConnected()) + { + IARM_Result_t res; + IARM_CHECK( IARM_Bus_UnRegisterEventHandler(IARMBUS_AUDIOCAPTUREMGR_NAME, DATA_CAPTURE_IARM_EVENT_AUDIO_CLIP_READY)); + } + } + + // Registered methods begin + uint32_t DataCapture::enableAudioCaptureWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + returnIfNumberParamNotFound(parameters, "bufferMaxDuration"); + + auto bufferMaxDuration = (unsigned int)parameters["bufferMaxDuration"].Number(); + + int ret = enableAudioCapture(bufferMaxDuration); + response["error"] = ret; + returnResponse(0 == ret); + } + + uint32_t DataCapture::getAudioClipWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + returnIfParamNotFound(parameters, "clipRequest"); + + Core::JSON::VariantContainer clipRequest; + if (parameters["clipRequest"].Content() == Core::JSON::Variant::type::STRING) + { + string clipRequestJSONString = parameters["clipRequest"].String(); + replace(clipRequestJSONString.begin(), clipRequestJSONString.end(), '\'', '\"'); + + Core::OptionalType error; + LOGINFO("Try parse '%s' as JSON string", clipRequestJSONString.c_str()); + if (!clipRequest.FromString(clipRequestJSONString, error)) + { + LOGERR("Failed parse JSON string from 'clipRequest': %s", error.Value().Message().c_str()); + returnResponse(false); + } + LOGWARN("Arguments received as string JSON object: %s", clipRequestJSONString.c_str()); + } + else if (parameters["clipRequest"].Content() != Core::JSON::Variant::type::OBJECT) + { + LOGERR("clipRequest' should have type STRING or OBJECT"); + returnResponse(false); + } + else + { + clipRequest = parameters["clipRequest"].Object(); + LOGINFO("Arguments received as parsed hash."); + } + + returnIfStringParamNotFound(clipRequest, "stream"); + returnIfStringParamNotFound(clipRequest, "url"); + returnIfNumberParamNotFound(clipRequest, "duration"); + returnIfStringParamNotFound(clipRequest, "captureMode"); + + int ret = getAudioClip(clipRequest); + response["error"] = ret; + returnResponse(0 == ret); + } + // Registered methods end + + // Internal methods begin + int DataCapture::enableAudioCapture(unsigned int bufferMaxDuration) + { + + LOGINFO("DataCaptureService calling enableAudioCapture: bufferMaxDuration = %d", (int)bufferMaxDuration); + + IARM_Result_t ret; + auto incoming_duration = static_cast (bufferMaxDuration); + + /*Are we being asked to turn this off?*/ + if(0 == incoming_duration) + { + /* Turn off whatever session you have going on.*/ + if(0 < _session_id) + { + iarmbus_acm_arg_t param; + param.session_id = _session_id; + + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_STOP, (void *) ¶m, sizeof(param)); + if(IARM_RESULT_SUCCESS != ret) + { + LOGERR("Failed to stop audiocapturemgr session."); + } + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_CLOSE, (void *) ¶m, sizeof(param)); + if(IARM_RESULT_SUCCESS != ret) + { + LOGERR("Failed to close audiocapturemgr session."); + } + _session_id = -1; + return ret; + } + } + + /*Open session*/ + if(0 > _session_id) + { + iarmbus_acm_arg_t param; + param.details.arg_open.source = 0; //primary + param.details.arg_open.output_type = BUFFERED_FILE_OUTPUT; + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_OPEN, (void *) ¶m, sizeof(param)); + if(!verify_result(ret, param)) + { + return ACM_RESULT_PRECAPTURE_NOT_SUPPORTED; + } + _session_id = param.session_id; + } + + /*Gather max duration information.*/ + { + iarmbus_acm_arg_t param; + param.session_id = _session_id; + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_GET_OUTPUT_PROPS, (void *) ¶m, sizeof(param)); + if(!verify_result(ret, param)) + { + LOGWARN("Unable to read max duration. Setting safe limit of 10"); + _max_supported_duration = 10; + } + else + { + _max_supported_duration = param.details.arg_output_props.output.max_buffer_duration; + } + } + + if(_max_supported_duration < incoming_duration) + { + LOGERR("Incoming precapture duration is too big."); + return (int)_max_supported_duration; // return max supported duration. + } + + /*Set precapture length*/ + { + iarmbus_acm_arg_t param; + param.session_id = _session_id; + param.details.arg_output_props.output.buffer_duration = incoming_duration; + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_SET_OUTPUT_PROPERTIES, (void *) ¶m, sizeof(param)); + if(!verify_result(ret, param)) + { + LOGERR("Failed to set precature duration."); + return ACM_RESULT_GENERAL_FAILURE; + } + } + + /*Get format of incoming sample */ + { + iarmbus_acm_arg_t param; + param.session_id = _session_id; + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_GET_AUDIO_PROPS, (void *) ¶m, sizeof(param)); + if(!verify_result(ret, param)) + { + LOGERR("Failed to get output properties."); + return ACM_RESULT_GENERAL_FAILURE; + } + else + { + _audio_properties = param.details.arg_audio_properties; + constructFormatString(); + } + } + + /*Start buffering*/ + { + iarmbus_acm_arg_t param; + param.session_id = _session_id ; + ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_START, (void *) ¶m, sizeof(param)); + if(!verify_result(ret, param)) + { + LOGERR("Failed to start audiocapture session"); + return ACM_RESULT_GENERAL_FAILURE; + } + return param.result; + } + } + + int DataCapture::getAudioClip(const JsonObject& clipRequest) + { + const string& stream = clipRequest["stream"].String(); + _destination_url = clipRequest["url"].String(); + _duration = (unsigned int)clipRequest["duration"].Number(); + const string& captureMode = clipRequest["captureMode"].String(); + _is_precapture = (captureMode == "preCapture"); + + LOGINFO("DataCaptureService calling getAudioClip: stream = %s, url = %s, duration = %d, captureMode = %s, session id = %d", + stream.c_str(), _destination_url.c_str(), _duration, captureMode.c_str(), _session_id); + + if(0 > _session_id) + { + LOGERR("Audio capture not enabled."); + return ACM_RESULT_GENERAL_FAILURE; + } + + if(stream != "primary") + { + LOGERR("Error! Audiocapture supports only primary audio."); + return ACM_RESULT_STREAM_UNAVAILABLE; + } + + iarmbus_acm_arg_t param; + param.session_id = _session_id; + param.details.arg_sample_request.duration = (float)_duration; + param.details.arg_sample_request.is_precapture = _is_precapture; + + /*For post-capture, verify duration is within bounds. For precapture, duration was fixed earlier*/ + if((!param.details.arg_sample_request.is_precapture) && (_max_supported_duration < _duration)) + { + LOGERR("Capture duration is too big."); + return ACM_RESULT_DURATION_OUT_OF_BOUNDS; // return max supported duration. + } + + IARM_Result_t ret = IARM_Bus_Call(IARMBUS_AUDIOCAPTUREMGR_NAME, IARMBUS_AUDIOCAPTUREMGR_REQUEST_SAMPLE, (void *) ¶m, sizeof(param)); + if(IARM_RESULT_SUCCESS != ret) + { + return ACM_RESULT_GENERAL_FAILURE; + } + return param.result; + } + + void DataCapture::constructFormatString() + { + _audio_format_string = ""; + switch(_audio_properties.format) + { + case acmFormate16BitStereo: + _audio_format_string += "codec=PCM_16_"; break; + case acmFormate16BitMonoLeft: //fall-through + case acmFormate16BitMonoRight: //fall-through + case acmFormate16BitMono: + _audio_format_string += "codec=PCM_1_16_"; break; + case acmFormate24BitStereo: + _audio_format_string += "codec=PCM_24_"; break; + case acmFormate24Bit5_1: + _audio_format_string += "codec=PCM_6_24_"; break; + default: + LOGERR("Unsupported audio format!"); + } + + switch(_audio_properties.sampling_frequency) + { + case acmFreqe48000: + _audio_format_string += "48000&"; break; + case acmFreqe44100: + _audio_format_string += "44100&"; break; + case acmFreqe32000: + _audio_format_string += "32000&"; break; + case acmFreqe24000: + _audio_format_string += "24000&"; break; + case acmFreqe16000: + _audio_format_string += "16000&"; break; + default: + LOGERR("Unsupported audio sampling rate!"); + } + LOGINFO("New format string is %s", _audio_format_string.c_str()); + } + + void DataCapture::iarmEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + pthread_mutex_lock(&_mutex); + LOGINFO("entry with owner: %s", owner); + + if( _instance != nullptr) + { + _instance->eventHandler(owner, eventId, data, len); + } + else + { + LOGERR("Failed handle IARM event: DataCapture instance is null"); + } + pthread_mutex_unlock(&_mutex); + } + + void DataCapture::eventHandler(const char *owner, IARM_EventId_t eventId, void *eventData, size_t len) + { + if(DATA_CAPTURE_IARM_EVENT_AUDIO_CLIP_READY == eventId) + { + iarmbus_notification_payload_t * payload = static_cast (eventData); + string dataLocator(payload->dataLocator); + string delimiter = "/"; + size_t pos = 0; + string fileName; + pos = dataLocator.rfind(delimiter); + fileName = dataLocator.substr(pos + delimiter.length(), dataLocator.length()); + int attemptsLeft = 2; + vector data; + int time_wait_sec = 1; + + JsonObject params; + params["fileName"] = fileName; + + while (attemptsLeft) { + if(0 == _sock_adaptor->connect_socket(payload->dataLocator)) + { + _sock_adaptor->get_data(data); // closes the socket + if (data.size() > 0) { + LOGINFO("Got a clip: %u bytes", data.size()); + break; + } else { + LOGWARN("No data in the socket. One more attempt in %d sec", time_wait_sec); + usleep(1000 * 1000 * time_wait_sec); + --attemptsLeft; + continue; + } + } + } + + if(data.size() > 0) + { + std::string error_str; + if (uploadDataToUrl(data, _destination_url.c_str(), error_str)) + { + params["status"] = true; + params["message"] = "Success"; + + } else { + LOGERR("Upload failed: %s (cURL error)", C_STR(error_str)); + params["status"] = false; + params["message"] = std::string("Upload Failed: ") + error_str; + } + + // Optionally, we can save a file +// FILE * pFile; +// const char* path = strcat(payload->dataLocator, ".received.pcm"); +// pFile = fopen (path, "wb"); +// fwrite (&data[0] , sizeof(unsigned char), data.size(), pFile); +// fclose (pFile); + // now, upload it, then remove: +// if (remove(path) != 0) +// { +// LOGERR("Unable to delete %s", path); +// } + } else { + LOGERR("Unable to read data from %s (connection error)", payload->dataLocator); + params["status"] = false; + params["message"] = std::string("Unable to read data from ") + string(payload->dataLocator); + } + + string message; + params.ToString(message); + LOGINFO("Sending notification %s: %s", C_STR(EVT_ON_AUDIO_CLIP_READY), C_STR(message)); + sendNotify(C_STR(EVT_ON_AUDIO_CLIP_READY), params); + } + } + + bool DataCapture::uploadDataToUrl(std::vector &data, const char *url, std::string &error_str) + { + CURL *curl; + CURLcode res; + bool call_succeeded = true; + + if(!url || !strlen(url)) + { + LOGERR("no url given"); + return false; + } + + LOGWARN("uploading pcm data of size %u to '%s'", data.size(), url); + + //init curl + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + if(!curl) + { + LOGERR("could not init curl\n"); + return false; + } + + //create header + struct curl_slist *chunk = NULL; + chunk = curl_slist_append(chunk, "Content-Type: audio/x-wav"); + + //set url and data + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &data[0]); + + //perform blocking upload call + res = curl_easy_perform(curl); + + //output success / failure log + if(CURLE_OK == res) + { + long response_code; + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + + if(600 > response_code && response_code >= 400) + { + LOGERR("uploading failed with response code %ld\n", response_code); + error_str = std::string("response code:") + std::to_string(response_code); + call_succeeded = false; + } + else + LOGWARN("upload done"); + } + else + { + LOGERR("upload failed with error %d:'%s'", res, curl_easy_strerror(res)); + error_str = std::to_string(res) + std::string(":'") + std::string(curl_easy_strerror(res)) + std::string("'"); + call_succeeded = false; + } + //clean up curl object + curl_easy_cleanup(curl); + curl_slist_free_all(chunk); + + return call_succeeded; + } + // Internal methods end + } // namespace Plugin +} // namespace WPEFramework diff --git a/DataCapture/DataCapture.h b/DataCapture/DataCapture.h new file mode 100644 index 0000000000..fd69c62e9c --- /dev/null +++ b/DataCapture/DataCapture.h @@ -0,0 +1,79 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include "Module.h" +#include "utils.h" +#include "AbstractPlugin.h" +#include "libIBus.h" +//#include "irMgr.h" + +class socket_adaptor; + +namespace WPEFramework { + namespace Plugin { + class DataCapture : public AbstractPlugin { + public: + DataCapture(); + virtual ~DataCapture(); + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override; + void eventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + + public/*members*/: + static DataCapture* _instance; + + public /*constants*/: + static const string SERVICE_NAME; + static const string METHOD_ENABLE_AUDIO_CAPTURE; + static const string METHOD_GET_AUDIO_CLIP; + static const string EVT_ON_AUDIO_CLIP_READY; + + private/*registered methods*/: + //methods + uint32_t enableAudioCaptureWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getAudioClipWrapper(const JsonObject& parameters, JsonObject& response); + + private/*internal methods*/: + DataCapture(const DataCapture&) = delete; + DataCapture& operator=(const DataCapture&) = delete; + + static void InitializeIARM(); + static void DeinitializeIARM(); + static void iarmEventHandler(const char *owner, IARM_EventId_t eventId, void *eventData, size_t len); + + int enableAudioCapture(unsigned int bufferMaxDuration); + int getAudioClip(const JsonObject& clipRequest); + void constructFormatString(); + bool uploadDataToUrl(std::vector &data, const char *url, std::string &error_str); + private/*members*/: + audiocapturemgr::session_id_t _session_id; + unsigned int _max_supported_duration; + socket_adaptor* _sock_adaptor; + audiocapturemgr::audio_properties_ifce_t _audio_properties; + string _audio_format_string; + string _destination_url; + bool _is_precapture; + unsigned int _duration; + static pthread_mutex_t _mutex; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/DataCapture/Module.cpp b/DataCapture/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/DataCapture/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/DataCapture/Module.h b/DataCapture/Module.h new file mode 100644 index 0000000000..4202453605 --- /dev/null +++ b/DataCapture/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME DataCapture +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/DataCapture/README.md b/DataCapture/README.md new file mode 100644 index 0000000000..6538a9790d --- /dev/null +++ b/DataCapture/README.md @@ -0,0 +1,27 @@ +# DataCapture + +## Versions +`org.rdk.dataCapture.1` + +## Methods: +``` +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "org.rdk.dataCapture.1.enableAudioCapture", "params":{"bufferMaxDuration":6}}' http://127.0.0.1:9998/jsonrpc +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc": "2.0", "id": "3", "method": "org.rdk.dataCapture.1.getAudioClip", "params": {"clipRequest": {"stream": "primary", "duration": 6, "captureMode": "preCapture", "url": "http://musicid.comcast.net/media-service-backend/analyze?trx=83cf6049-b722-4c44-b92e-79a504ae8f85:1458580048400&codec=PCM_16_16K&deviceId=5082732351093257712"}}}' http://127.0.0.1:9998/jsonrpc +``` +## Events +``` +onAudioClipReady +``` +## Responses +``` +enableAudioCapture: +{"jsonrpc":"2.0","id":3,"result":{"error":0,"success":true} + +getAudioClip: +{"jsonrpc":"2.0","id":3,"result":{"error":0,"success":true} + +onAudioClipReady: +{"fileName":"acm-songid0","status":false,"message":"Unable to read data from /tmp/acm-songid0"} +``` +## Full Reference +https://etwiki.sys.comcast.net/display/RDK/DataCapture diff --git a/DataCapture/socket_adaptor.cpp b/DataCapture/socket_adaptor.cpp new file mode 100644 index 0000000000..1f9a8b59bf --- /dev/null +++ b/DataCapture/socket_adaptor.cpp @@ -0,0 +1,447 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "socket_adaptor.h" +#include +#include +#include +#include +#include +#include + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include + +static const int PIPE_READ_FD = 0; +static const int PIPE_WRITE_FD = 1; +static const unsigned int MAX_CONNECTIONS = 1; + +static bool g_one_time_init_complete = false; + +socket_adaptor::socket_adaptor() : m_listen_fd(-1), m_write_fd(-1), m_read_fd(-1), m_num_connections(0), m_callback(nullptr) +{ + SA_INFO("Enter\n"); + if(!g_one_time_init_complete) + { + /*SIGPIPE must be ignored or process will exit when client closes connection*/ + struct sigaction sig_settings; + sig_settings.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sig_settings, NULL); + g_one_time_init_complete = true; + } + REPORT_IF_UNEQUAL(0, pipe2(m_control_pipe, O_NONBLOCK)); +} + +socket_adaptor::~socket_adaptor() +{ + SA_INFO("Enter\n"); + stop_listening(); + close(m_control_pipe[PIPE_WRITE_FD]); + close(m_control_pipe[PIPE_READ_FD]); +} + +int socket_adaptor::connect_socket(const std::string &path) +{ + int ret = 0; + + /*Open new UNIX socket to transfer data*/ + m_read_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if( m_read_fd != -1) + { + SA_INFO("Socket created.\n"); + struct sockaddr_un address; + + /* start with a clean address structure */ + memset(&address, 0, sizeof(struct sockaddr_un)); + address.sun_family = AF_UNIX; + snprintf(address.sun_path, 108, path.c_str()); + if(connect(m_read_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un)) == 0) + { + SA_INFO("socket connected: %s\n", path.c_str()); + } else { + SA_ERR("connect() failed\n"); + close(m_read_fd); + ret = -1; + return ret; + + } + } + else + { + SA_ERR("Could not create socket.\n"); + ret = -1; + } + return ret; +} + +int socket_adaptor::write_data(const char * buffer, const unsigned int size) +{ + int ret = write(m_write_fd, buffer, size); + if(0 > ret) + { + SA_WARN("Write SA_ERR! Closing socket. errno: 0x%x\n", errno); + SA_ERR("socket_adaptor::data_callback() "); + + close(m_write_fd); + lock(); + if(0 < m_write_fd) + { + m_write_fd = -1; + m_num_connections--; + } + unlock(); + } + else if((unsigned int)ret != size) + { + SA_WARN("Incomplete buffer write!\n"); + } + return ret; +} + +unsigned int socket_adaptor::fetch_data() +{ + unsigned int size_recv , total_size = 0, n = 0; + unsigned short int CHUNK_SIZE = 4096; + char chunk[CHUNK_SIZE]; + + if(m_read_fd < 0) { + SA_ERR("Unable to fetch data. Did you connect?"); + return -1; + } + + m_fetch_buffer.clear(); + while(1) + { + memset(chunk ,0 , CHUNK_SIZE); + if((size_recv = read(m_read_fd , chunk , CHUNK_SIZE) ) <= 0) + { + break; + } + else + { + m_fetch_buffer.insert(m_fetch_buffer.end(), chunk, chunk + size_recv); + total_size += size_recv; + ++n; + } + } + SA_WARN("%d bytes received in %u reads!\n", total_size, n); + + close(m_read_fd); + lock(); + if(0 < m_read_fd) + { + m_read_fd = -1; + } + unlock(); + + return total_size; +} + +void socket_adaptor::get_data(std::vector& data) +{ + if (m_fetch_buffer.empty()) + { + if (fetch_data() < 0) + { + SA_WARN("Empty call."); + return; + } + } + data = m_fetch_buffer; + m_fetch_buffer.clear(); +} + +unsigned int socket_adaptor::get_data(char * buffer, const unsigned int size) +{ + unsigned int total_size = m_fetch_buffer.size(); + unsigned int ret_size = 0; + + if (m_fetch_buffer.empty() || size == 0) + { + SA_WARN("Empty call. Forgot to fetch data?"); + return 0; + } + + if (size < total_size) + { + ret_size = size; + SA_WARN("Data won't fit in the supplied buffer, %d bytes lost\n", total_size - ret_size); + } else { + ret_size = total_size; + } + memcpy(buffer, m_fetch_buffer.data(), ret_size); + m_fetch_buffer.clear(); + return ret_size; +} + +std::string& socket_adaptor::get_path() +{ + return m_path; +} + +int socket_adaptor::start_listening(const std::string &path) +{ + int ret = 0; + m_path = path; + + /*Open new UNIX socket to transfer data*/ + m_listen_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if(0 < m_listen_fd) + { + SA_INFO("Socket created.\n"); + + for(int retries = 0; retries < 2; retries++) //If first attempt fails due to path in use, unlink it and try again. + { + struct sockaddr_un bind_path; + bind_path.sun_family = AF_UNIX; + strncpy(bind_path.sun_path, m_path.c_str(), sizeof(bind_path.sun_path)); + + SA_INFO("Binding to path %s\n", bind_path.sun_path); + ret = bind(m_listen_fd, (const struct sockaddr *) &bind_path, sizeof(bind_path)); + if(-1 == ret) + { + if(EADDRINUSE == errno) + { + SA_WARN("path is already in use. Using brute force.\n"); + unlink(m_path.c_str()); + } + else + { + SA_ERR("Failed to bind to path. SA_ERR is %d\n", errno); + SA_ERR("bind SA_ERR"); + close(m_listen_fd); + m_listen_fd = -1; + break; + } + } + else + { + SA_INFO("Bound successfully to path.\n"); + REPORT_IF_UNEQUAL(0, listen(m_listen_fd, 3)); + m_thread = std::thread(&socket_adaptor::worker_thread, this); + break; + } + } + } + else + { + SA_ERR("Could not open socket.\n"); + ret = -1; + } + return ret; +} + +int socket_adaptor::stop_listening() +{ + SA_INFO("Enter\n"); + lock(); + + /*Shut down worker thread that listens to incoming connections.*/ + if(m_thread.joinable()) + { + int message = EXIT; + int ret = write(m_control_pipe[PIPE_WRITE_FD], &message, sizeof(message)); + if(ret != sizeof(message)) + { + SA_ERR("Couldn't trigger worker thread shutdown.\n"); + } + else + { + SA_INFO("Waiting for worker thread to join.\n"); + unlock(); + m_thread.join(); + lock(); + SA_INFO("Worker thread has joined.\n"); + } + } + if(!m_path.empty()) + { + if(0 < m_write_fd) + { + close(m_write_fd); + m_write_fd = -1; + m_num_connections--; + SA_INFO("Closed write fd. Total active connections now is %d\n", m_num_connections); + } + SA_INFO("Removing named socket %s.\n", m_path.c_str()); + unlink(m_path.c_str()); + m_path.clear(); + } + unlock(); + SA_INFO("Exit\n"); + return 0; +} + +void socket_adaptor::process_new_connection() +{ + SA_INFO("Enter\n"); + lock(); + if(0 < m_write_fd) + { + SA_WARN("Trashing old write socket in favour of new one.\n"); + close(m_write_fd); + m_write_fd = -1; + m_num_connections--; + } + + SA_INFO("Setting up a new connection.\n"); + struct sockaddr_un incoming_addr; + int addrlen = sizeof(incoming_addr); + m_write_fd = accept(m_listen_fd, (sockaddr *) &incoming_addr, (socklen_t *)&addrlen); + + if(0 > m_write_fd) + { + SA_ERR("SA_ERR accepting connection.\n"); + } + else + { + m_num_connections++; + SA_INFO("Connected to new client.\n"); + } + unlock(); + + if(m_callback) + { + m_callback(m_callback_data); + } + return; +} + +void socket_adaptor::worker_thread() +{ + SA_INFO("Enter\n"); + int control_fd = m_control_pipe[PIPE_READ_FD]; + int max_fd = (m_listen_fd > control_fd ? m_listen_fd : control_fd); + + + bool check_fds = true; + + while(check_fds) + { + fd_set poll_fd_set; + FD_ZERO(&poll_fd_set); + FD_SET(m_listen_fd, &poll_fd_set); + FD_SET(control_fd, &poll_fd_set); + + int ret = select((max_fd + 1), &poll_fd_set, NULL, NULL, NULL); + SA_INFO("Unblocking now. ret is 0x%x\n", ret); + if(0 == ret) + { + SA_ERR("select() returned 0.\n"); + } + else if(0 < ret) + { + //Some activity was detected. Process event further. + if(0 != FD_ISSET(control_fd, &poll_fd_set)) + { + control_code_t message; + REPORT_IF_UNEQUAL((sizeof(message)), read(m_control_pipe[PIPE_READ_FD], &message, sizeof(message))); + if(EXIT == message) + { + SA_INFO("Exiting monitor thread.\n"); + break; + } + else + { + process_control_message(message); + } + } + if(0 != FD_ISSET(m_listen_fd, &poll_fd_set)) + { + process_new_connection(); + } + } + else + { + SA_ERR("SA_ERR polling monitor FD!\n"); + break; + } + } + + SA_INFO("Exit\n"); +} + +void socket_adaptor::terminate_current_connection() +{ + lock(); + if(0 < m_write_fd) + { + close(m_write_fd); + m_write_fd = -1; + m_num_connections--; + SA_INFO("Terminated current connection.\n"); + } + unlock(); +} + + +void socket_adaptor::lock() +{ + m_mutex.lock(); +} + +void socket_adaptor::unlock() +{ + m_mutex.unlock(); +} + +void socket_adaptor::process_control_message(control_code_t message) +{ + if(NEW_CALLBACK == message) + { + lock(); + auto num_connections = m_num_connections; + auto callback = m_callback; + auto callback_data = m_callback_data; + unlock(); + + if(callback && (0 != num_connections)) + { + callback(callback_data); + } + } +} + + +unsigned int socket_adaptor::get_active_connections() +{ + lock(); + unsigned int num_conn = m_num_connections; + unlock(); + return num_conn; +} + +void socket_adaptor::register_data_ready_callback(socket_adaptor_cb_t callback, void *data) +{ + lock(); + m_callback = callback; + m_callback_data = data; + unlock(); + + if(nullptr != callback) + { + control_code_t message = NEW_CALLBACK; + int ret = write(m_control_pipe[PIPE_WRITE_FD], &message, sizeof(message)); + UNUSED(ret); + } +} diff --git a/DataCapture/socket_adaptor.h b/DataCapture/socket_adaptor.h new file mode 100644 index 0000000000..acd4ce7988 --- /dev/null +++ b/DataCapture/socket_adaptor.h @@ -0,0 +1,149 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#ifndef _socket_adaptor_H_ +#define _socket_adaptor_H_ +#include +#include +#include +#include +#include +#include + +#define SA_INFO(fmt, ...) do { fprintf(stderr, "[%d] SA_INFO [%s:%d] %s: " fmt "\n", (int)syscall(SYS_gettid), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); fflush(stderr); } while (0) +#define SA_WARN(fmt, ...) do { fprintf(stderr, "[%d] SA_WARN [%s:%d] %s: " fmt "\n", (int)syscall(SYS_gettid), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); fflush(stderr); } while (0) +#define SA_ERR(fmt, ...) do { fprintf(stderr, "[%d] SA_ERROR [%s:%d] %s: " fmt "\n", (int)syscall(SYS_gettid), __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); fflush(stderr); } while (0) + +#ifndef REPORT_IF_UNEQUAL +#define REPORT_IF_UNEQUAL(lhs, rhs) do { \ + if((lhs) != (rhs)) SA_ERR("Unexpected error!\n");}while(0); +#endif + +#define UNUSED(expr)(void)(expr) + +typedef void (*socket_adaptor_cb_t)(void * data); + +class socket_adaptor +{ + public: + typedef enum + { + EXIT = 0, + NEW_CALLBACK, + CODE_MAX + } control_code_t; + + private: + std::string m_path; + int m_listen_fd; + int m_write_fd; + int m_read_fd; + int m_control_pipe[2]; + std::vector m_fetch_buffer; + unsigned int m_num_connections; + std::thread m_thread; + std::mutex m_mutex; + socket_adaptor_cb_t m_callback; + void * m_callback_data; + + void process_new_connection(); + void process_control_message(control_code_t message); + int stop_listening(); + void lock(); + void unlock(); + void worker_thread(); + + public: + socket_adaptor(); + ~socket_adaptor(); + /** + * @brief This api connects to the socket for reading data + * + * @param[in] path socket to connect to. + * + * @return Returns 0 on success or -1 o + */ + int connect_socket(const std::string &path); + + /** + * @brief This function makes the audiocapturemgr listen for incoming unix domain connections to the given path. + * + * @param[in] path binding path. + * + * @return Returns 0 on success, appropiate error code otherwise. + */ + int start_listening(const std::string &path); + + /** + * @brief This api returns the data path. + * + * It is the path of unix domain server that ip-out clients must connect to in order to receive real-time audio data. + * + * @return Returns the data path in string. + */ + std::string& get_path(); + + /** + * @brief This api invokes unix write() to write data to the socket. + * + * @param[in] buffer Data buffer. + * @param[in] size Size of the buffer + * + * @return Returns 0 on success, appropiate errorcode otherwise. + */ + int write_data(const char * buffer, const unsigned int size); + + /** + * @brief This api invokes unix read() to read all data from the socket into the internal buffer + * + * + * @return Returns length of the data or -1 in case of an error + */ + unsigned int fetch_data(); + + /** + * @brief This api provides the previously fetched data + * + * + * @return void + */ + void get_data(std::vector& data); + + /** + * @brief This api provides the previously fetched data + * + * + * @return Returns 0 in case of success, positive number of bytes left in case of the supplied buffer is less than all data or -1 in case of an error + */ + unsigned int get_data(char * buffer, const unsigned int size); + + /** + * @brief This api invokes close() to terminate the current connection. + */ + void terminate_current_connection(); //handy to simulate EOS at the other end. + + /** + * @brief This api returns the number of active connections. + * + * @return Returns the number of active connections. + */ + unsigned int get_active_connections(); + void register_data_ready_callback(socket_adaptor_cb_t cb, void *data); +}; +#endif //_socket_adaptor_H_ diff --git a/Network/CMakeLists.txt b/Network/CMakeLists.txt new file mode 100644 index 0000000000..9575a92559 --- /dev/null +++ b/Network/CMakeLists.txt @@ -0,0 +1,45 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME Network) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + Network.cpp + NetUtils.cpp + NetUtilsNetlink.cpp + NetworkTraceroute.cpp + PingNotifier.cpp + Module.cpp + ../helpers/utils.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +find_package(IARMBus) + +target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) +target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES}) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/Network/Module.cpp b/Network/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/Network/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/Network/Module.h b/Network/Module.h new file mode 100644 index 0000000000..1c72da95b8 --- /dev/null +++ b/Network/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME Network +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/Network/NetUtils.cpp b/Network/NetUtils.cpp new file mode 100644 index 0000000000..5a32e525e4 --- /dev/null +++ b/Network/NetUtils.cpp @@ -0,0 +1,1171 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "NetUtils.h" +#include +#include +#include +//#include +#include +#include +#include +#include +#include "Network.h" + +//Defines +#define DELETE_ROUTE_CMD "route delete default gw %s 2>&1" //192.168.1.1 +#define RESTART_DHCPC_CMD "/sbin/udhcpc -i %s -p /tmp/udhcpc.%s.pid 2>&1 &" //eth0:0, eth0:0 + +#define NETUTIL_DEVICE_PROPERTIES_FILE "/etc/device.properties" +#define NETUTIL_PERSIST_DEFAULT_INTERFACE_FILE "/opt/persistent/defaultInterface" +#define ESTB_IPV6_FILE_NAME "/tmp/estb_ipv6" +#define ESTB_IPV4_FILE_NAME "/tmp/estb_ipv4" +#define COMMAND_RESULT_FILE "cmdresult" + +#define CONNECTED_FLAGS (IFF_UP|IFF_RUNNING) +#define INTERFACE_CONNECTED(flags) (((flags & CONNECTED_FLAGS) == CONNECTED_FLAGS) ? 1 : 0) + + +namespace WPEFramework { + namespace Plugin { + + unsigned int NetUtils::m_counter = 0; + std::mutex NetUtils::m_counterProtect; + + /* + * + */ + NetUtils::NetUtils() : + m_dataProtect(), + m_netlinkProtect() + { + } + + NetUtils::~NetUtils() + { + _stopMonitor(); + } + + /* + * Initialise netutils: + * - initialise the interface list + * - check (and set) the persistent default interface + * - start the network monitor task + */ + void NetUtils::InitialiseNetUtils() + { + std::string defaultInterface; + + // Initialise interface list (this is required for setting the default interface) + _initialiseInterfaceList(); + //_displayInterfaceStatus(); + + // If we are persisting the default interface, try to set it now (on boot this is unlikely + // to work as the interfaces will probably not be up yet) + if (NetUtils::getDefaultGatewayPersistent(defaultInterface)) + { + if (!SetDefaultInterface(defaultInterface, true)) + { + LOGINFO("Setting %s as the default interface is pending...", defaultInterface.c_str()); + + // Store the setting to be applied when the interfaces are ready + _setPendingDefaultInterface(defaultInterface); + } + } + + // Start the network monitor task + _startMonitor(); + } + + /* + * Get the MAC address of the interface + */ + bool NetUtils::GetInterfaceMACAddress(const std::string &interfaceDescription, std::string &macAddr) + { + std::lock_guard lock(m_dataProtect); + + for ( auto &info : interfaceList) + { + if (info.m_if_descr == interfaceDescription) + { + macAddr = info.m_if_macaddr; + return true; + } + } + + return false; + } + + /* + * Get the connection state of the interface + */ + bool NetUtils::GetInterfaceConnected(const std::string &interfaceDescription, bool &connected) + { + std::lock_guard lock(m_dataProtect); + + for ( auto &info : interfaceList) + { + if (info.m_if_descr == interfaceDescription) + { + connected = INTERFACE_CONNECTED(info.m_if_flags); + return true; + } + } + + return false; + } + + /* + * Get the name of the interface (e.g. wlan0, wlan0:0, eth0, eth0:0) + * If physical==false then a virtual interface name may be returned if one exists + */ + bool NetUtils::GetInterfaceName(const std::string &interfaceDescription, std::string &name, bool physical) + { + std::lock_guard lock(m_dataProtect); + + for ( auto &info : interfaceList) + { + if (info.m_if_descr == interfaceDescription) + { + name = info.interfaceName(physical); + return true; + } + } + + return false; + } + + /* + * Get the descriptive name for the default interface (e.g. WIFI, ETHERNET etc.) + */ + bool NetUtils::GetDefaultInterfaceDescription(std::string &description) + { + stringList interfaces; + stringList gateways; + stringList descriptions; + + if (!_getDefaultInterfaces(interfaces, gateways, descriptions)) + { + LOGERR("Failed to get default interface information."); + } + else + { + description = descriptions[0]; // assume single default interface + return true; + } + + return false; + } + + /* + * Internal method to request the default route(s) information (from netlink) and match + * it to our return list to get the interface name, description etc. + * If physical=true return the name of the physical interface (eg. eth0) + * else it may be a virtual interface if one exists (eg. eth0:0) + * If async=true then the netlink request will be run in a separate thread + */ + bool NetUtils::_getDefaultInterfaces(stringList &interfaces, stringList &gateways, stringList &descriptions, + bool physical, bool async) + { + // Serialise requests so we only open one netlink request socket at a time + std::lock_guard lock(m_netlinkProtect); + + indexList interfaceIndexList; + bool success = false; + + if (async) + { + // Async indicates to make the netlink call in a thread (this is to do with how the socket + // pid is defined - we can only have one netlink socket per thread/process) + _asyncQueryDefaultInterfaces(interfaceIndexList, gateways, success); + } + else + { + _queryDefaultInterfaces(interfaceIndexList, gateways, success); + } + + if (!success) + { + LOGERR("Failed to send route information request."); + } + else + { + std::lock_guard lock(m_dataProtect); + + for ( auto &index : interfaceIndexList) + { + for ( auto &info : interfaceList) + { + if (info.m_if_index == (int)index) + { + interfaces.push_back(info.interfaceName(physical)); + descriptions.push_back(info.m_if_descr); + LOGINFO("Default route: %s, %s", + info.m_if_descr.c_str(), info.interfaceName().c_str()); + return true; + } + } + } + } + + return false; + } + + /* + * Make a netlink request to get the default interfaces (in a separate thread) + */ + void NetUtils::_asyncQueryDefaultInterfaces(indexList &interfaces, stringList &gateways, bool &success) + { + std::thread asyncTask = std::thread(_queryDefaultInterfaces, + std::ref(interfaces), std::ref(gateways), std::ref(success)); + + if (asyncTask.joinable()) + asyncTask.join(); + } + + /* + * Make a netlink request to get the default interfaces + */ + void NetUtils::_queryDefaultInterfaces(indexList &interfaces, stringList &gateways, bool &success) + { + Netlink netlinkRequest; + + success = false; + + if (!netlinkRequest.connect()) + { + LOGERR("Failed to connect netlink request socket"); + } + else if (NetUtils::isConfiguredIPV6()) + { + if (!netlinkRequest.getDefaultInterfaces(interfaces, gateways, true)) + { + LOGWARN("Failed to get default IPv6 interfaces"); + } + else + { + success = true; + } + } + else if (!netlinkRequest.getDefaultInterfaces(interfaces, gateways, false)) + { + LOGWARN("Failed to get default interfaces"); + } + else + { + success = true; + } + } + + + /* + * Set the default interface + * + * NOTE - The flag pendingRequest is used to indicate that we are trying to set the default + * interface at startup, probably at boot time. In this case we will only proceed if the + * interface is connected and we have a default interface. (As this may be called from the + * monitor task we will perform the netlink request asynchronously in a separate thread) + */ + bool NetUtils::SetDefaultInterface(std::string &interfaceDescription, bool onInit) + { + std::string interfaceName = ""; + bool connected = false; + bool success = false; + + // Check the interface exists and is connected (UP and RUNNING) + if (!GetInterfaceName(interfaceDescription, interfaceName) || + !GetInterfaceConnected(interfaceDescription, connected)) + { + LOGERR("Interface not recognised"); + } + else if (!connected) + { + LOGERR("Interface is not connected"); + } + else + { + stringList names; + stringList gateways; + stringList descriptions; + + // Get a list of current default interfaces (routes) + // We want the name of the virtual interface if it exists + if (!_getDefaultInterfaces(names, gateways, descriptions, false, onInit)) + { + LOGWARN("Could not get current default interface"); + + // If this is on initialisation (we may be waiting for the network connections to + // be set up) then exit if we do not have a default interfaces (yet) + if (onInit) + { + return false; + } + } + + // Kill the current udhcpc processes and delete the default interfaces + for (unsigned i = 0; i < names.size(); i++) + { + if (interfaceDescription == descriptions[i]) + { + LOGINFO("%s is already a default interface", interfaceDescription.c_str()); + success = true; + } + else + { + LOGINFO("Deleting default interface on %s", names[i].c_str()); + _deleteDefaultInterface(names[i], gateways[i]); + } + } + + if (!success) + { + // Start udhcpc on the requested interface + success = _createDefaultInterface(interfaceName); + } + + if (success) + { + // Make sure any pending setting is cleared + _clearPendingDefaultInterface(); + } + } + + return success; + } + + /* + * Delete the current default interface (route) + */ + bool NetUtils::_deleteDefaultInterface(std::string &name, std::string &gateway) + { + std::string output = ""; + char command[MAX_COMMAND_LENGTH]; + bool success = true; + + // Terminate udhcpc on the interface + snprintf(command, MAX_COMMAND_LENGTH, "cat /tmp/udhcpc.%s.pid 2>&1", name.c_str()); + if (NetUtils::execCmd(command, output) < 0) + { + LOGERR("Failed to get udhcpc pid"); + } + else if (output.length() > 0) //pid of udhcpc + { + snprintf(command, MAX_COMMAND_LENGTH, "kill -9 %s 2>&1", output.c_str()); + NetUtils::execCmd(command, output); + } + + // Delete the default route + if (gateway.length() > 0) + { + snprintf(command, MAX_COMMAND_LENGTH, DELETE_ROUTE_CMD, gateway.c_str()); + if (NetUtils::execCmd(command, output) < 0) + { + LOGERR("Failed to delete default route"); + success = false; + } + } + + return success; + } + + /* + * Set the default interface (route) + */ + bool NetUtils::_createDefaultInterface(std::string &name) + { + std::string output = ""; + char command[MAX_COMMAND_LENGTH]; + + // Request a dhcp lease for the new interface + snprintf(command, MAX_COMMAND_LENGTH, RESTART_DHCPC_CMD, + name.c_str(), name.c_str()); + + if (NetUtils::execCmd(command, output) < 0) + { + LOGERR("Failed to create default route"); + return false; + } + else + { + return true; + } + } + + /* + * Returns >= 0 on success + * Blocking process so run in background thread + * + * If 'result' is not NULL then it will be set to the return status of the command + * + * If 'outputfile' is not NULL then it should contain a name for the outpur file. The output of the command will + * be dumped to '/tmp/[outputfile]x' where x is a unique number. + * The filepath will be returned in 'output' and the file should be deleted by the calling process after use. + */ + int NetUtils::execCmd(const char *command, std::string &output, bool *result, const char *outputfile) + { + std::string commandString; + std::string resultFile; + char buffer[MAX_OUTPUT_LENGTH]; + FILE *pipe = NULL; + size_t length = 0; + + output.clear(); + + commandString.assign(command); + + // If we have an output file then pipe the output from the command to /tmp/outpufile + if (outputfile) + { + getTmpFilename(outputfile, output); //append a count to get a unique filename and return the filename in output + commandString += " > "; + commandString += output; + } + + // If we have requested a result, store the result of the command in /tmp/cmdresult + if (result) + { + getTmpFilename(COMMAND_RESULT_FILE, resultFile); //append a count to get a unique filename + commandString += "; echo $? > "; + commandString += resultFile; + } + + pipe = popen(commandString.c_str(), "r"); + if (pipe == NULL) + { + LOGERR("%s: failed to open file '%s' for read mode with result: %s", __FUNCTION__, + commandString.c_str(), strerror(errno)); + return -1; + } + + LOGWARN("%s: opened file '%s' for read mode", __FUNCTION__, + commandString.c_str()); + + while (!feof(pipe) && fgets(buffer, MAX_OUTPUT_LENGTH, pipe) != NULL) + { + // If we are not dumping it to file, store the output + if (!outputfile) + { + output += buffer; + } + } + + // Strip trailing line feed from the output + if ((length = output.length()) > 0) + { + if (output[length-1] == '\n') + { + output.erase(length-1); + } + } + + // If we have requested a result, query the contents of the result file + if (result) + { + std::string commandResult = "1"; + if (!getFile(resultFile.c_str(), commandResult, true)) //delete file after reading + { + LOGERR("%s: failed to get command result '%s'", __FUNCTION__,resultFile.c_str()); + } + else + { + *result = (commandResult == "0"); + LOGINFO("%s: command result '%s'", __FUNCTION__,((*result)?"true":"false")); + } + } + + return pclose(pipe); + } + + /* + * See if an address is IPV4 format + */ + bool NetUtils::isIPV4(const std::string &address) + { + struct in_addr ipv4address; + return (inet_pton(AF_INET, address.c_str(), &ipv4address) > 0); + } + + /* + * See if an address is IPV6 format + */ + bool NetUtils::isIPV6(const std::string &address) + { + struct in6_addr ipv6address; + return (inet_pton(AF_INET6, address.c_str(), &ipv6address) > 0); + } + + /* + * See if the device is configured to use ipv6 + */ + bool NetUtils::isConfiguredIPV6() + { + struct stat buffer; + return (stat (ESTB_IPV6_FILE_NAME, &buffer) == 0); + } + + /* + * See if the device is configured to use ipv4 + */ + bool NetUtils::isConfiguredIPV4() + { + struct stat buffer; + return (stat (ESTB_IPV4_FILE_NAME, &buffer) == 0); + } + + /* + * Get the CMTS (default) interface name + */ +#ifdef USE_NETLINK + bool NetUtils::getCMTSInterface(std::string &interface) + { + stringList interfaces; + stringList gateways; + stringList descriptions; + + // Get the name and ip address for the default interface + if (!_getDefaultInterfaces(interfaces, gateways, descriptions)) + { + LOGERR("Failed to get default interface information."); + return false; + } + else + { + interface = interfaces[0]; + return true; + } + } +#else + bool NetUtils::getCMTSInterface(std::string &interface) + { + //Try to use DEFAULT_ESTB_INTERFACE but this may not exist on all devices + if (!NetUtils::envGetValue("DEFAULT_ESTB_INTERFACE", interface)) + { + //Query netsrvmgr for the active interface + std::string interfaceDesription = ""; + if (!Network::getInstance()->_getActiveInterface(interfaceDesription)) + { + LOGERR("%s: failed to get active interface", __FUNCTION__); + return false; + } + //... and convert to interface name + else if (!GetInterfaceName(interfaceDesription, interface, true)) //ignore virtual interfaces + { + LOGERR("%s: failed to get active interface name", __FUNCTION__); + return false; + } + } + return true; + } +#endif + + /* + * Get the CMTS gateway from configured information + */ +#ifdef USE_NETLINK + bool NetUtils::getCMTSGateway(std::string &gateway) + { + stringList interfaces; + stringList gateways; + stringList descriptions; + + gateway = ""; + + // Get the name and ip address for the default interface + if (!_getDefaultInterfaces(interfaces, gateways, descriptions)) + { + LOGERR("Failed to get default interface information."); + return false; + } + else + { + gateway = gateways[0]; // assume single default interface + return true; + } + } +#else + bool NetUtils::getCMTSGateway(std::string &gateway) + { + std::string interface = ""; + char cmd [1000] = {0x0}; + + gateway = ""; + + if (getCMTSInterface(interface)) + { + if (isConfiguredIPV6()) + { + snprintf(cmd, sizeof(cmd), "route -A inet6 | grep %s | grep 'UG' | awk '{print $2}'", interface.c_str()); + if (execCmd(cmd, gateway) < 0) + { + LOGERR("%s: failed to get IPv6 gateway address", __FUNCTION__); + } + } + + // If we are not configured for ipv6 or didn't get a gateway address, default to ipv4 and try that + if (gateway.length() == 0) + { + snprintf(cmd, sizeof(cmd), "route -n | grep %s | grep 'UG' | awk '{print $2}'", interface.c_str()); + if (execCmd(cmd, gateway) < 0) + { + LOGERR("%s: failed to get IPv4 gateway address", __FUNCTION__); + } + } + + if (gateway.length() > 0) + { + // We can get multiple lines matching (especially ipv6) so delete all after the first + size_t lfpos = gateway.find('\n'); + if (lfpos != std::string::npos) + { + gateway.erase(lfpos); + } + + LOGINFO("gateway = %s", gateway.c_str()); + } + } + + return (gateway.length() > 0); + } +#endif + + /* + * Set the persistence state of the default interface + */ + bool NetUtils::setDefaultGatewayPersistent(const char *interface) + { + bool result = false; + if (interface) + { + std::ofstream ofs(NETUTIL_PERSIST_DEFAULT_INTERFACE_FILE); + if (ofs.is_open()) + { + ofs << interface; + result = ofs.fail(); + ofs.close(); + } + } + else + { + std::remove(NETUTIL_PERSIST_DEFAULT_INTERFACE_FILE); + result = true; + } + return result; + } + + /* + * Get the persistence state of the default interface + */ + bool NetUtils::getDefaultGatewayPersistent(std::string &interface) + { + return getFile(NETUTIL_PERSIST_DEFAULT_INTERFACE_FILE, interface); + } + + + /* + * Get the contents of a file + * if deleteFile is true, remove the file after it is read + * (default == false) + */ + bool NetUtils::getFile(const char *filepath, std::string &contents, bool deleteFile) + { + bool result = false; + if (filepath) + { + std::ifstream ifs(filepath); + if (ifs.is_open()) + { + std::getline(ifs, contents); + result = contents.length() > 0; + ifs.close(); + + if (deleteFile) + { + std::remove(filepath); + } + } + } + return result; + } + + /* + * Given the interface name, check the device file so we try to match what the lower layers + * expect/deliver (i.e stay in sync with existing methods) + */ + bool NetUtils::_envGetInterfaceDescription(const char *name, std::string &description) + { + description.clear(); + + if (envCheckBool("WIFI_SUPPORT")) + { + if (envCheckValue("WIFI_INTERFACE", name)) + { + description = "WIFI"; + return true; + } + } + if (envCheckBool("MOCA_SUPPORT")) + { + if (envCheckValue("MOCA_INTERFACE", name)) + { + description = "MOCA"; + return true; + } + } + if (envCheckValue("ETHERNET_INTERFACE", name)) + { + description = "ETHERNET"; + return true; + } + + return false; + } + + /* + * Get the value of the given key from the environment (device properties file) + */ + bool NetUtils::envGetValue(const char *key, std::string &value) + { + std::ifstream fs(NETUTIL_DEVICE_PROPERTIES_FILE, std::ifstream::in); + std::string::size_type delimpos; + std::string line; + + if (!fs.fail()) + { + while (std::getline(fs, line)) + { + if (!line.empty() && + ((delimpos = line.find('=')) > 0)) + { + std::string itemKey = line.substr(0, delimpos); + if (itemKey == key) + { + value = line.substr(delimpos + 1, std::string::npos); + return true; + } + } + } + } + + return false; + } + + bool NetUtils::envCheckValue(const char *key, const char *value) + { + std::string envValue = ""; + if (!envGetValue(key, envValue)) + { + LOGWARN("Could not find property: %s", key); + } + return (envValue == value); + } + + bool NetUtils::envCheckBool(const char *key) + { + std::string envValue = ""; + if (!envGetValue(key, envValue)) + { + LOGWARN("Could not find property: %s", key); + } + return (envValue == "true"); + } + + bool NetUtils::_parseMACAddress(unsigned char *addr, int addlen, std::string &macAddr) + { + for (int i = 0; i < addlen; i++) + { + char buffer[4]; + sprintf(buffer, "%s%02x", i ? ":" : "", addr[i]); + buffer[3] = '\0'; + + macAddr += buffer; + } + + return macAddr.length() > 0; + } + + /* + * Initialise the data that we use to monitor for network changes and provide + * information on interfaces + */ + void NetUtils::_initialiseInterfaceList() + { + std::lock_guard lock(m_netlinkProtect); + struct ifaddrs *pIntfList = NULL; + struct ifaddrs *pIntf = NULL; + std::string description; + + getifaddrs(&pIntfList); + if (pIntfList) + { + // Use AF_PACKET items to determine the physical interfaces + for (pIntf = pIntfList; pIntf != NULL; pIntf = pIntf->ifa_next) + { + if (pIntf->ifa_addr->sa_family == AF_PACKET) + { + struct sockaddr_ll *hw = (struct sockaddr_ll *)pIntf->ifa_addr; + struct iface_info info; + info.m_if_name = pIntf->ifa_name; + info.m_if_index = hw->sll_ifindex; + info.m_if_flags = pIntf->ifa_flags; + + // We do not need set the address and flags details here + info.m_if_addr = ""; + info.m_if_addrv6 = ""; + + info.m_if_vname = ""; + info.m_if_vaddr = ""; + info.m_if_vaddrv6 = ""; + + if ( hw->sll_hatype != ARPHRD_ETHER) + { + // Ignore non-ethernet ineterfaces (lo, sit0 etc.) + LOGINFO("Not ethernet interface: %s", pIntf->ifa_name); + } + // Look for the interface name in the environment file + else if (!_envGetInterfaceDescription(pIntf->ifa_name, info.m_if_descr)) + { + // We expect the interfcaes to be defined in the device.properties file + LOGERR("No description for interface %s", pIntf->ifa_name); + } + else if (!_parseMACAddress(hw->sll_addr, hw->sll_halen, info.m_if_macaddr)) + { + LOGERR("Could not parse mac address for interface %s", pIntf->ifa_name); + } + else + { + LOGINFO("Storing interface %s, %s, %s", info.m_if_name.c_str(), info.m_if_descr.c_str(), info.m_if_macaddr.c_str()); + interfaceList.push_back(info); + } + } + } + + // Use Update the interface list with addresses (and virtual interfaces) + for ( auto &info : interfaceList) + { + for (pIntf = pIntfList; pIntf != NULL; pIntf = pIntf->ifa_next) + { + if ((pIntf->ifa_addr->sa_family == AF_INET) || + (pIntf->ifa_addr->sa_family == AF_INET6)) + { + if ((0 == strncmp(info.m_if_name.c_str(), pIntf->ifa_name, info.m_if_name.length())) && + (0 != strcmp(info.m_if_name.c_str(), pIntf->ifa_name))) + { + info.m_if_vname = pIntf->ifa_name; + } + } + } + } + + freeifaddrs(pIntfList); + } + } + + /* + * Background monitor methods + */ + + /* + * Start a background network monitor task + */ + void NetUtils::_startMonitor() + { + m_fdNlMonitorMsgPipe[0] = -1; + m_fdNlMonitorMsgPipe[1] = -1; + + /* Create a pipe for posting a shutdown request */ + if (0 == pipe(m_fdNlMonitorMsgPipe)) + { + m_netlinkMonitorThread = std::thread(_runMonitor, this); + } + else + { + LOGERR("ERROR: Failed to create netlink monitor abort pipe."); + } + } + + /* + * Stop the background network monitor task + */ + void NetUtils::_stopMonitor() + { + if (m_fdNlMonitorMsgPipe[1] >= 0) + { + if (m_netlinkMonitorThread.joinable()) + { + /* Post a shutdown request then wait for thread to terminate */ + if (write(m_fdNlMonitorMsgPipe[1], &m_fdNlMonitorMsgPipe[1], sizeof(int)) != sizeof(int)) + { + LOGERR("ERROR: Failed to write shutdown request. Netlink monitor task will not terminate."); + } + LOGINFO("Joining netlink connection monitor thread."); + m_netlinkMonitorThread.join(); + LOGINFO("Netlink connection monitor thread terminated."); + } + + close(m_fdNlMonitorMsgPipe[0]); + close(m_fdNlMonitorMsgPipe[1]); + m_fdNlMonitorMsgPipe[0] = -1; + m_fdNlMonitorMsgPipe[1] = -1; + } + } + + /* + * Netlink monitor task + * This is currently used just to detect that a change has occurred, the details will + * be identified in _updateInterfaceStatus() using getifaddrs + */ + void NetUtils::_runMonitor(NetUtils *utils) + { + char msgBuffer[NETLINK_MESSAGE_BUFFER_SIZE]; + int msgLength; + struct pollfd pfds[2]; + Netlink netlink; + + LOGWARN("%s : Netlink monitor RUNNING...", __FUNCTION__); + + // Connect a netlink socket to the groups we want to monitor + if (!netlink.connect(RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR)) + { + LOGERR("Could not connect to netlionk socket."); + return; + } + + // first fd is the message pipe + pfds[0].fd = utils->m_fdNlMonitorMsgPipe[0]; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + + pfds[1].fd = netlink.sockfd(); + pfds[1].events = POLLIN; + pfds[1].revents = 0; + + utils->_lock(); + utils->_resetInterfaceList(); + + while (1) + { + utils->_updateInterfaceStatus(); + //utils->_displayInterfaceStatus(); + + // Check to see if we are waiting to set the default interface (on startup) + utils->_checkPendingDefaultInterface(); + utils->_unlock(); + + // wait for an event + if (poll(pfds, 2, -1) <= 0) + { + LOGWARN("Netlink socket poll returned no events"); + continue; + } + + utils->_lock(); + + if (pfds[0].revents) // Check for an event on our shutdown pipe + { + LOGINFO("Shutting down netlink monitor"); + break; + } + else if ((msgLength = netlink.read(msgBuffer, NETLINK_MESSAGE_BUFFER_SIZE)) < static_cast(sizeof(struct nlmsghdr)) ) + { + // Checking netlink messages. 0=no msg & peer shutdown, -1=error, see errno + LOGERR("Invalid netlink message (retval %d)", msgLength); + std::this_thread::sleep_for (std::chrono::milliseconds(100)); + } + else + { + // At least one event relating to ip address or connection status was received. Log what types. + netlink.displayMessages(msgBuffer, msgLength); + } + } + + utils->_unlock(); + + LOGWARN("%s : ENDED.", __FUNCTION__); + } + + /* + * Notifications from monitor thread + */ + + void NetUtils::_interfaceFlagsUpdated(struct iface_info &info) + { + JsonObject params; + + params["interface"] = info.m_if_descr; + params["status"] = INTERFACE_CONNECTED(info.m_if_flags) ? "CONNECTED" : "DISCONNECTED"; + Network::_instance->_asyncNotifyConnection(params); + } + + void NetUtils::_interfaceAddressUpdated(struct iface_info &info, std::string &addr) + { + JsonObject params; + + params["interface"] = info.m_if_descr; + params["ip6Address"] = info.ipv6Addr(); + params["ip4Address"] = info.ipv4Addr(); + params["status"] = (addr.length() == 0) ? "LOST" : "ACQUIRED"; + Network::_instance->_asyncNotifyIPAddr(params); + } + + /* + * Reset any information needed to trigger a notification message on update + */ + void NetUtils::_resetInterfaceList() + { + for ( auto &info : interfaceList) + { + info.m_if_flags = IFF_RUNNING; + info.m_if_addr = ""; + info.m_if_addrv6 = ""; + } + } + + /* + * Update the interfaces and notify of changes to connection state or ip address + */ + void NetUtils::_updateInterfaceStatus() + { + struct ifaddrs * pIntfList=NULL; + struct ifaddrs * pIntf=NULL; + + getifaddrs(&pIntfList); + + /* For each interface in our list, see if the address or connection status has changed */ + for ( auto &info : interfaceList) + { + int if_flags = 0; + char ipAddr[INET6_ADDRSTRLEN]; + std::string ipv4Addr = info.ipv4Addr(); + std::string ipv6Addr = info.ipv6Addr(); + if_flags = info.m_if_flags; + + info.m_if_addr = ""; + info.m_if_addrv6 = ""; + info.m_if_vname = ""; + info.m_if_vaddr = ""; + info.m_if_vaddrv6 = ""; + + for (pIntf = pIntfList; pIntf != NULL; pIntf = pIntf->ifa_next) + { + // Match the address to the interface via the interface name + // Note - we will assume the format of a virtual interface name is just 'name:0' (e.g. 'eth0:0') + // so we will match the interface name to the first part of the address interface name + if (0 == strncmp(info.m_if_name.c_str(), pIntf->ifa_name, info.m_if_name.length())) + { + info.m_if_flags = pIntf->ifa_flags; + + if ((pIntf->ifa_addr->sa_family == AF_INET) || + (pIntf->ifa_addr->sa_family == AF_INET6)) + { + inet_ntop(pIntf->ifa_addr->sa_family, &((struct sockaddr_in *)pIntf->ifa_addr)->sin_addr, ipAddr, INET6_ADDRSTRLEN); + + // If this is a virtual interface then the names will not match over the full length + if (0 != strcmp(info.m_if_name.c_str(), pIntf->ifa_name)) + { + info.m_if_vname = pIntf->ifa_name; + + if (pIntf->ifa_addr->sa_family == AF_INET) + { + info.m_if_vaddr = ipAddr; + } + else if (pIntf->ifa_addr->sa_family == AF_INET6) + { + info.m_if_vaddrv6 = ipAddr; + } + } + else + { + if (pIntf->ifa_addr->sa_family == AF_INET) + { + info.m_if_addr = ipAddr; + } + else if (pIntf->ifa_addr->sa_family == AF_INET6) + { + info.m_if_addrv6 = ipAddr; + } + } + } + } + } + + /* + * See if anything has changed + */ + if (INTERFACE_CONNECTED(info.m_if_flags) != INTERFACE_CONNECTED(if_flags)) + { + _interfaceFlagsUpdated(info); + } + + if (ipv4Addr != info.ipv4Addr()) + { + _interfaceAddressUpdated(info, info.ipv4Addr()); + } + else if (ipv6Addr != info.ipv6Addr()) + { + _interfaceAddressUpdated(info, info.ipv6Addr()); + } + } + } + + /* + * Check to see if we have stored a default interface to set when we are able + */ + void NetUtils::_checkPendingDefaultInterface() + { + if (!NetUtils::isConfiguredIPV6() && //TBD support for default interface on IPv6 + !m_pendingDefaultInterface.empty()) + { + LOGINFO("Try setting %s as the default interface...", m_pendingDefaultInterface.c_str()); + SetDefaultInterface(m_pendingDefaultInterface, true); + } + } + + void NetUtils::getTmpFilename(const char *in, std::string &out) + { + std::lock_guard lock(m_counterProtect); + out = "/tmp/"; + out += in; + out += std::to_string(m_counter++); + } + + /* + * Debug info + */ + void NetUtils::_displayInterfaceStatus() + { + for ( auto &info : interfaceList) + { + LOGINFO ("<%.40s>, %.40s, UP = %s, RUNNING = %s\n", + info.m_if_descr.c_str(), + info.m_if_macaddr.c_str(), + info.m_if_flags & IFF_UP ? "YES" : "NO", + info.m_if_flags & IFF_RUNNING ? "YES" : "NO"); + LOGINFO ("> <%.40s>, IP v4 Address = %.40s, IP v6 Address = %.40s.\n", + info.m_if_name.c_str(), + info.m_if_addr.c_str(), + info.m_if_addrv6.c_str()); + if (!info.m_if_name.empty()) + LOGINFO ("> <%.40s>, IP v4 Address = %.40s, IP v6 Address = %.40s.\n", + info.m_if_vname.c_str(), + info.m_if_vaddr.c_str(), + info.m_if_vaddrv6.c_str()); + } + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/NetUtils.h b/Network/NetUtils.h new file mode 100644 index 0000000000..4c665450ca --- /dev/null +++ b/Network/NetUtils.h @@ -0,0 +1,178 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include +#include +#include +#include +#include +#include "utils.h" +#include "NetUtilsNetlink.h" + +namespace WPEFramework { + #define MAX_COMMAND_LENGTH 256 + #define MAX_OUTPUT_LENGTH 128 + + namespace Plugin { + class Network; + + class iface_info + { + public: + iface_info() {;} + virtual ~iface_info() {;} + + int m_if_index; // interface index + std::string m_if_descr; // interface descriptive name + std::string m_if_macaddr; // mac address + int m_if_flags; // SIOCGIFFLAGS + + std::string m_if_name; // interface name + std::string m_if_addr; // ip address v4 + std::string m_if_addrv6; // ip address v6 + + std::string m_if_vname; // virtual interface name + std::string m_if_vaddr; // virtual ip address v4 + std::string m_if_vaddrv6; // virtual ip address v6 + + std::string &ipv6Addr() + { + if (m_if_vname.length() && m_if_vaddrv6.length()) + { + return m_if_vaddrv6; + } + return m_if_addrv6; + } + std::string &ipv4Addr() + { + if (m_if_vname.length() && m_if_vaddr.length()) + { + return m_if_vaddr; + } + return m_if_addr; + } + std::string &interfaceName(bool physical = false) + { + if (physical || (m_if_vname.length() == 0)) + { + return m_if_name; // return the name of the physical interface + } + else + { + return m_if_vname; // return the name of the virtual interface + } + } + }; + + + class NetUtils { + private: + std::vector interfaceList; + + void _startMonitor(); + void _stopMonitor(); + static void _runMonitor(NetUtils *utils); + + void _resetInterfaceList(); + void _updateInterfaceStatus(); + void _interfaceFlagsUpdated(struct iface_info &info); + void _interfaceAddressUpdated(struct iface_info &info, std::string &addr); + void _checkPendingDefaultInterface(); + + void _displayInterfaceStatus(); + + bool _envGetInterfaceDescription(const char *name, std::string &description); + bool _getDefaultInterfaces(stringList &interfaces, stringList &gateways, stringList &descriptions, + bool physical = true, bool async = false); + + void _asyncQueryDefaultInterfaces(indexList &interfaces, stringList &gateways, bool &success); + static void _queryDefaultInterfaces(indexList &interfaces, stringList &gateways, bool &success); + + void _initialiseInterfaceList(); + bool _parseMACAddress(unsigned char *addr, int addlen, std::string &macAddr); + + void _lock() + { + m_dataProtect.lock(); + } + + void _unlock() + { + m_dataProtect.unlock(); + } + + void _setPendingDefaultInterface(std::string &interface) + { + std::lock_guard lock(m_dataProtect); + m_pendingDefaultInterface = interface; + } + void _clearPendingDefaultInterface() + { + std::lock_guard lock(m_dataProtect); + m_pendingDefaultInterface.empty(); + } + + public: + NetUtils(); + virtual ~NetUtils(); + + void InitialiseNetUtils(); + bool GetInterfaceMACAddress(const std::string &interfaceDescription, std::string &macAddr); + bool GetInterfaceConnected(const std::string &interfaceDescription, bool &connected); + bool GetInterfaceName(const std::string &interfaceDescription, std::string &name, bool physical = false); + + bool GetDefaultInterfaceDescription(std::string &description); + bool SetDefaultInterface(std::string &interfaceDescription, bool onInit = false); + + bool getCMTSInterface(std::string &interface); + bool getCMTSGateway(std::string &gateway); + + static bool isIPV4(const std::string &address); + static bool isIPV6(const std::string &address); + static bool isConfiguredIPV6(); + static bool isConfiguredIPV4(); + static int execCmd(const char *command, std::string &output, bool *result = NULL, const char *outputfile = NULL); + static bool setDefaultGatewayPersistent(const char *interface); + static bool getDefaultGatewayPersistent(std::string &interface); + static bool getFile(const char *filepath, std::string &contents, bool deleteFile = false); + + static bool envGetValue(const char *key, std::string &value); + static bool envCheckValue(const char *key, const char *value); + static bool envCheckBool(const char *key); + + static void getTmpFilename(const char *in, std::string &out); + + private: + std::thread m_netlinkMonitorThread; + std::recursive_mutex m_dataProtect; + std::mutex m_netlinkProtect; + int m_fdNlMonitorMsgPipe[2]; + bool m_monitorRunning; + std::string m_pendingDefaultInterface; + + static unsigned int m_counter; + static std::mutex m_counterProtect; + + static bool _createDefaultInterface(std::string &name); + static bool _deleteDefaultInterface(std::string &name, std::string &gateway); + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/NetUtilsNetlink.cpp b/Network/NetUtilsNetlink.cpp new file mode 100644 index 0000000000..197f8bab96 --- /dev/null +++ b/Network/NetUtilsNetlink.cpp @@ -0,0 +1,377 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "NetUtilsNetlink.h" +#include +#include +#include +#include +#include + +namespace WPEFramework { + namespace Plugin { + + Netlink::Netlink() : + m_fdNetlink(-1), + m_netlinkProtect() + { + } + + Netlink::~Netlink() + { + if (m_fdNetlink != -1) + { + close(m_fdNetlink); + m_fdNetlink = -1; + } + } + + /* + * Create a netlink socket + * Note - the socket is set to non-blocking so use select/poll to manage waiting and timeout + * - only one netlink connection should be made per thread (nl_pid should be unique) + */ + bool Netlink::connect(int groups) + { + std::lock_guard lock(m_netlinkProtect); + + memset(&m_nlSockaddr, 0, sizeof(m_nlSockaddr)); + m_nlSockaddr.nl_family = AF_NETLINK; + m_nlSockaddr.nl_groups = groups; + m_nlSockaddr.nl_pid = pthread_self(); + m_nlSockaddr.nl_pid <<= 16; + m_nlSockaddr.nl_pid += getpid(); + + m_fdNetlink = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (m_fdNetlink == -1) + { + LOGERR("Failed to create Netlink socket"); + return false; + } + + if (bind(m_fdNetlink, (struct sockaddr *)&m_nlSockaddr, sizeof(m_nlSockaddr)) < 0) + { + LOGERR("Failed to bind to Netlink socket: %s", strerror(errno)); + close(m_fdNetlink); + m_fdNetlink = -1; + return false; + } + + if (fcntl(m_fdNetlink, F_SETFL, O_NONBLOCK) == -1) + { + LOGWARN("Failed to set Netlink socket to non-blocking"); + } + + return true; + } + + /* + * Read data from the socket + * Wait for msTimeout milliseconds for data (do not wait if msTimeout is zero) + * Note - this will not block if data is not currently available + */ + int Netlink::read(char *buffer, int size) + { + std::lock_guard lock(m_netlinkProtect); + return _getMessage(buffer, size, 0); + } + + /* + * Get the default interface from netlink + */ + bool Netlink::getDefaultInterfaces(indexList &interfaceIndex, stringList &gatewayAddress, bool ipv6) + { + std::lock_guard lock(m_netlinkProtect); + if (!_sendRouteRequest(ipv6)) + { + LOGERR("Failed to send route information request."); + } + else if (!_getRoutesInformation(interfaceIndex, gatewayAddress)) + { + LOGINFO("Failed to find default route information."); + } + else + { + return true; + } + + return false; + } + + /* + * DEBUG function to log netlink messages in buffer + */ + void Netlink::displayMessages(const char* msgBuffer, int msgLength) + { + struct nlmsghdr *nlhdr; + nlhdr = (struct nlmsghdr *) msgBuffer; + struct ifinfomsg *ifinfo; + + // Display any netlink messages received. + for (nlhdr = (struct nlmsghdr *)msgBuffer; + NLMSG_OK(nlhdr, msgLength); + nlhdr = NLMSG_NEXT(nlhdr, msgLength)) + { + if (nlhdr->nlmsg_type == NLMSG_DONE) + { + break; + } + switch (nlhdr->nlmsg_type) + { + case RTM_DELADDR: + LOGINFO("Netlink DELADDR EVENT..."); + break; + case RTM_DELLINK: + LOGINFO("Netlink DISCONNECTION EVENT..."); + break; + case RTM_NEWLINK: + LOGINFO("Netlink NEWLINK EVENT..."); + break; + case RTM_NEWADDR: + LOGINFO("Netlink NEWADDR EVENT..."); + break; + default: + LOGINFO("Netlink OTHER EVENT (%d)...",nlhdr->nlmsg_type); + break; + } + + if ( nlhdr->nlmsg_type == RTM_NEWLINK ) + { + ifinfo = (struct ifinfomsg *)NLMSG_DATA(nlhdr); + if ((ifinfo->ifi_flags & IFF_UP) && + (ifinfo->ifi_flags & IFF_RUNNING)) + { + LOGINFO("EVENT TYPE = CONNECTED..."); + } + } + } + } + + + /* + * Internal functions + */ + + /* + * Wait for data returned by the socket for specified time + */ + bool Netlink::_waitForReply(unsigned ms) + { + struct timeval timeout; + fd_set readFDSet; + + FD_ZERO(&readFDSet); + FD_SET(m_fdNetlink, &readFDSet); + + timeout.tv_sec = (ms / 1000); + timeout.tv_usec = ((ms % 1000) * 1000); + + if (select(m_fdNetlink + 1, &readFDSet, NULL, NULL, &timeout) > 0) + { + return FD_ISSET(m_fdNetlink, &readFDSet); + } + return false; + } + + int Netlink::_getMessage(char *buffer, int size, unsigned msTimeout) + { + struct iovec iov; + struct msghdr msg; + int replyLength = -1; + + if (msTimeout) + { + if (!_waitForReply(msTimeout)) + { + LOGERR("No message received"); + return false; + } + } + + iov.iov_base = buffer; + iov.iov_len = size; + + msg.msg_name = &m_nlSockaddr; + msg.msg_namelen = sizeof(m_nlSockaddr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if ((replyLength = recvmsg(m_fdNetlink, &msg, 0)) < static_cast(sizeof(struct nlmsghdr))) + { + LOGERR("Unable to read message"); + replyLength = -1; + } + + return replyLength; + } + + /* + * Functions for requesting and processing network route information + */ + + /* + * Send a netlink request for all network route information + */ + bool Netlink::_sendRouteRequest(bool ipv6) + { + struct messageBuffer { + struct nlmsghdr netlinkRequesthdr; + struct rtmsg request; + } requestMessage; + + memset(&requestMessage, 0, sizeof(struct messageBuffer)); + + requestMessage.netlinkRequesthdr.nlmsg_type = RTM_GETROUTE; + requestMessage.netlinkRequesthdr.nlmsg_len = sizeof(requestMessage); + requestMessage.netlinkRequesthdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + requestMessage.netlinkRequesthdr.nlmsg_seq = 1; + requestMessage.request.rtm_family = ipv6 ? AF_INET6 : AF_INET; + + if (send(m_fdNetlink, &requestMessage, sizeof(requestMessage), 0) < 0) + { + LOGERR("Failed to send socket message: %s", strerror(errno)); + return false; + } + + return true; + } + + /* + * Retrieve the route information data and parse it to retrieve the default route + */ + bool Netlink::_getRoutesInformation(indexList &defaultInterfaceIndex, stringList &gatewayAddress) + { + char msgBuffer[NETLINK_MESSAGE_BUFFER_SIZE]; + struct nlmsghdr *nlhdr; + int msgLength = -1; + unsigned index = 0; + std::string destination = ""; + std::string gateway = ""; + + defaultInterfaceIndex.clear(); + gatewayAddress.clear(); + + /* Retrieve Netlink messages */ + if ((msgLength = _getMessage(msgBuffer, NETLINK_MESSAGE_BUFFER_SIZE, NETLINK_MESSAGE_TIMEOUT_MS)) < 0 ) + { + LOGERR("Unable to read message"); + return false; + } + + nlhdr = (struct nlmsghdr *)msgBuffer; + + for (nlhdr = (struct nlmsghdr *)msgBuffer; + NLMSG_OK(nlhdr, msgLength); + nlhdr = NLMSG_NEXT(nlhdr, msgLength)) + { + if (nlhdr->nlmsg_type == NLMSG_DONE) + { + break; + } + else if (nlhdr->nlmsg_type == RTM_NEWROUTE) + { + if (_parseRoute(nlhdr, index, destination, gateway)) + { + if (destination.length() == 0) // default route has no destination address + { + LOGINFO("Default interface found: %d, %s", index, gateway.c_str()); + gatewayAddress.push_back(gateway); + defaultInterfaceIndex.push_back(index); + } + } + } + else + { + // Ignore other message types + } + } + + return !defaultInterfaceIndex.empty(); + } + + /* + * Parse the route message returning the interface index and the destination address + */ + bool Netlink::_parseRoute(void *msg, unsigned &index, std::string &destination, std::string &gateway) + { + struct nlmsghdr *nlhdr = (struct nlmsghdr *)msg; + struct rtmsg *routeMsg; + struct rtattr *attribute; + int attrLength = -1; + char ipAddress[INET6_ADDRSTRLEN]; + + routeMsg = (struct rtmsg *)NLMSG_DATA(nlhdr); + + if (routeMsg->rtm_table != RT_TABLE_MAIN) + { + // we only want the main routing table information + return false; + } + + if ((routeMsg->rtm_family != AF_INET) && (routeMsg->rtm_family != AF_INET6)) + { + return false; + } + + index = 0; + + attrLength = nlhdr->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg)); + for (attribute = RTM_RTA(routeMsg); + RTA_OK(attribute, attrLength); + attribute = RTA_NEXT(attribute, attrLength)) + { + if (attribute->rta_type == RTA_OIF) + { + index = *(unsigned *)RTA_DATA(attribute); + } + else if (attribute->rta_type == RTA_DST) + { + inet_ntop(routeMsg->rtm_family, RTA_DATA(attribute), ipAddress, INET6_ADDRSTRLEN); + destination = ipAddress; + } + else if (attribute->rta_type == RTA_GATEWAY) + { + inet_ntop(routeMsg->rtm_family, RTA_DATA(attribute), ipAddress, INET6_ADDRSTRLEN); + gateway = ipAddress; + } + else + { + // ignore the rest for now + } + } + + //TBD - how to correctly determine the IPV6 gateway / default route? + if (routeMsg->rtm_family == AF_INET6) + { + if ((routeMsg->rtm_scope == RT_SCOPE_UNIVERSE) && + (routeMsg->rtm_type == RTN_UNICAST) && + (routeMsg->rtm_protocol == RTPROT_RA)) + { + destination = ""; + } + } + + return index > 0; + } + + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/NetUtilsNetlink.h b/Network/NetUtilsNetlink.h new file mode 100644 index 0000000000..3271da5b06 --- /dev/null +++ b/Network/NetUtilsNetlink.h @@ -0,0 +1,64 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include +#include +#include +#include "utils.h" + +namespace WPEFramework { + namespace Plugin { + #define NETLINK_MESSAGE_BUFFER_SIZE 8192 + #define NETLINK_MESSAGE_TIMEOUT_MS 500 + + typedef std::vector stringList; + typedef std::vector indexList; + + /* + * This is a helper class to manage a netlink socket used to get info about various network + * parameters + */ + class Netlink + { + public: + Netlink(); + virtual ~Netlink(); + + bool connect(int groups = 0); + int read(char *buffer, int size); + bool getDefaultInterfaces(indexList &interfaceIndex, stringList &gatewayAddress, bool ipv6 = false); + int sockfd() { return m_fdNetlink;} + + void displayMessages(const char* msgBuffer, int msgLength); + + private: + int m_fdNetlink; + struct sockaddr_nl m_nlSockaddr; + std::mutex m_netlinkProtect; + + bool _waitForReply(unsigned ms); + bool _sendRouteRequest(bool ipv6 = false); + int _getMessage(char *buffer, int size, unsigned msTimeout); + bool _getRoutesInformation(indexList &defaultInterfaceIndex, stringList &gatewayAddress); + bool _parseRoute(void *msg, unsigned &index, std::string &destination, std::string &gateway); + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/Network.config b/Network/Network.config new file mode 100644 index 0000000000..9d1ba4ca01 --- /dev/null +++ b/Network/Network.config @@ -0,0 +1,3 @@ +set (autostart false) +set (preconditions Platform) +set (callsign "org.rdk.Network") diff --git a/Network/Network.cpp b/Network/Network.cpp new file mode 100644 index 0000000000..4c24cbe3ed --- /dev/null +++ b/Network/Network.cpp @@ -0,0 +1,650 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Network.h" + +// Event types +#define EVT_NETWORK_CONNECTION_STATUS_UPDATE "onConnectionStatusChanged" +#define EVT_NETWORK_CONNECTION_IP_ADDRESS_UPDATE "onIPAddressStatusChanged" + + +/* Netsrvmgr Based Macros & Structures */ +#define IARM_BUS_NM_SRV_MGR_NAME "NET_SRV_MGR" +#define IARM_BUS_NETSRVMGR_API_getActiveInterface "getActiveInterface" +#define IARM_BUS_NETSRVMGR_API_getNetworkInterfaces "getNetworkInterfaces" +#define IARM_BUS_NETSRVMGR_API_isInterfaceEnabled "isInterfaceEnabled" +#define IARM_BUS_NETSRVMGR_API_getInterfaceControlPersistence "getInterfaceControlPersistence" +#define IARM_BUS_NETSRVMGR_API_getSTBip "getSTBip" +#define INTERFACE_SIZE 10 +#define INTERFACE_LIST 50 +#define MAX_IP_ADDRESS_LEN 46 +#define DEFAULT_PING_PACKETS 15 + +typedef struct _IARM_BUS_NetSrvMgr_Iface_EventData_t { + union { + char activeIface[INTERFACE_SIZE]; + char allNetworkInterfaces[INTERFACE_LIST]; + char enableInterface[INTERFACE_SIZE]; + char activeIfaceIpaddr[MAX_IP_ADDRESS_LEN]; + }; + char interfaceCount; + bool isInterfaceEnabled; +} IARM_BUS_NetSrvMgr_Iface_EventData_t; + +typedef enum _NetworkManager_EventId_t { + IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_ENABLED=50, + IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_CONTROL_PERSISTENCE, + IARM_BUS_NETWORK_MANAGER_MAX, +} IARM_Bus_NetworkManager_EventId_t; + +namespace WPEFramework +{ + namespace Plugin + { + SERVICE_REGISTRATION(Network, 1, 0); + Network* Network::_instance = nullptr; + + Network::Network() : PluginHost::JSONRPC() + { + LOGWARN ("Entering %s \n", __FUNCTION__); + Network::_instance = this; + + /* HardCode it for now; wait for Set API Version call to update this further */ + m_apiVersionNumber = 1; + + // Quirk + Register("getQuirks", &Network::getQuirks, this); + + // Network_API_Version_1 + Register("getInterfaces", &Network::getInterfaces, this); + Register("isInterfaceEnabled", &Network::isInterfaceEnabled, this); + Register("setInterfaceEnabled", &Network::setInterfaceEnabled, this); + Register("getDefaultInterface", &Network::getDefaultInterface, this); + Register("setDefaultInterface", &Network::setDefaultInterface, this); + + Register("getStbIp", &Network::getStbIp, this); + + Register("setApiVersionNumber", &Network::setApiVersionNumberWrapper, this); + Register("getApiVersionNumber", &Network::getApiVersionNumberWrapper, this); + + Register("trace", &Network::trace, this); + Register("traceNamedEndpoint", &Network::traceNamedEndpoint, this); + + Register("getNamedEndpoints", &Network::getNamedEndpoints, this); + + Register("ping", &Network::ping, this); + Register("pingNamedEndpoint", &Network::pingNamedEndpoint, this); + + + m_netUtils.InitialiseNetUtils(); + } + + Network::~Network() + { + LOGWARN("Destructor of Network_%d", m_apiVersionNumber); + + Unregister("getQuirks"); + Unregister("getInterfaces"); + Unregister("isInterfaceEnabled"); + Unregister("setInterfaceEnabled"); + Unregister("getDefaultInterface"); + Unregister("setDefaultInterface"); + Unregister("getStbIp"); + Unregister("setApiVersionNumber"); + Unregister("getApiVersionNumber"); + Unregister("trace"); + Unregister("traceNamedEndpoint"); + Unregister("getNamedEndpoints"); + Unregister("ping"); + Unregister("pingNamedEndpoint"); + + m_apiVersionNumber = 0; + Network::_instance = NULL; + } + + const string Network::Initialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + + Utils::IARM::init(); + + return string(); + } + + void Network::Deinitialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + } + + string Network::Information() const + { + LOGWARN ("Entering %s \n", __FUNCTION__); + // No additional info to report. + return(string()); + } + + // Wrapper methods + uint32_t Network::getQuirks(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + JsonArray array; + array.Add("RDK-20093"); + response["quirks"] = array; + returnResponse(true); + } + + uint32_t Network::setApiVersionNumberWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (parameters.HasLabel("version")) + { + getNumberParameter("version", m_apiVersionNumber); + returnResponse(true); + } + returnResponse(false); + } + + uint32_t Network::getApiVersionNumberWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + response["version"] = m_apiVersionNumber; + returnResponse(true); + } + + uint32_t Network::getInterfaces (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + IARM_Result_t ret = IARM_RESULT_SUCCESS; + IARM_BUS_NetSrvMgr_Iface_EventData_t param; + ret = IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME,IARM_BUS_NETSRVMGR_API_getNetworkInterfaces, (void*)¶m, sizeof(param)); + if (ret == IARM_RESULT_SUCCESS) + { + if(param.interfaceCount) + { + std::stringstream strm(param.allNetworkInterfaces); + std::string interfaceName = ""; + JsonArray networkInterfaces; + + /* The list of interfaces from netsrvmgr just returns a comma separated list of WIFI, ETHERNET etc. + * We need extra information for this api so we will get it from other calls to netsrvmgr and from + * the network monitoring information we have acquired. + */ + while (std::getline(strm, interfaceName, ',')) + { + std::string macAddr = ""; + bool enabled = false; + bool connected = false; + JsonObject interface; + + if (!_getInterfaceEnabled(interfaceName, enabled)) + { + LOGERR("Failed to get enabled state for %s", interfaceName.c_str()); + } + if (!_getInterfaceMACAddress(interfaceName, macAddr)) + { + LOGERR("Failed to get MAC address for %s", interfaceName.c_str()); + } + if (!_getInterfaceConnected(interfaceName, connected)) + { + LOGERR("Failed to get connected state for %s", interfaceName.c_str()); + } + + interface["interface"] = interfaceName; + interface["macAddress"] = macAddr; + interface["enabled"] = enabled; + interface["connected"] = connected; + + networkInterfaces.Add(interface); + } + + response["interfaces"] = networkInterfaces; + } + else + { + response["interfaces"] = std::string(); + } + + returnResponse(true); + } + else + { + LOGWARN ("Call to %s for %s failed\n", IARM_BUS_NM_SRV_MGR_NAME, __FUNCTION__); + } + } + else + LOGWARN ("This version of Network Software is not supporting this API..\n"); + + returnResponse(false); + } + + uint32_t Network::getDefaultInterface (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + std::string interface = ""; + +#ifdef USE_NETLINK + //TBD - check netlink method gets the correct interface on IPv6 + if (m_netUtils.GetDefaultInterfaceDescription(interface)) + { + response["interface"] = interface; + returnResponse(true); + } +#else + if (_getActiveInterface(interface)) + { + response["interface"] = interface; + returnResponse(true); + } +#endif + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::setDefaultInterface (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + //TBD - for now, we will only support this for IPv4, how to do this if configured for ipv6? + if (!NetUtils::isConfiguredIPV6()) + { + std::string interface = ""; + bool persist = false; + + if (parameters.HasLabel("interface")) + { + getStringParameter("interface", interface); + + if (m_netUtils.SetDefaultInterface(interface)) + { + if (parameters.HasLabel("persist")) + { + getBoolParameter("persist", persist); + if (!NetUtils::setDefaultGatewayPersistent(persist ? interface.c_str(): NULL)) + { + LOGERR("Failed to persist default interface (%s)", interface.c_str()); + } + } + + returnResponse(true); + } + } + else + { + LOGWARN ("No interface specified\n"); + } + } + else + { + LOGWARN ("Device is not configured for IPv4\n"); + } + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::getStbIp(const JsonObject ¶meters, JsonObject &response) + { + IARM_Result_t ret = IARM_RESULT_SUCCESS; + IARM_BUS_NetSrvMgr_Iface_EventData_t param; + memset(¶m, 0, sizeof(param)); + ret = IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_getSTBip, (void*)¶m, sizeof(param)); + + if (ret != IARM_RESULT_SUCCESS ) + { + response["ip"] = ""; + returnResponse(false); + } + response["ip"] = std::string(param.activeIfaceIpaddr, MAX_IP_ADDRESS_LEN-1); + returnResponse(true); + } + + uint32_t Network::isInterfaceEnabled (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + std::string interface = ""; + bool enabled = false; + + if (parameters.HasLabel("interface")) + { + getStringParameter("interface", interface); + + if (_getInterfaceEnabled(interface, enabled)) + { + response["enabled"] = enabled; + returnResponse(true); + } + } + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::setInterfaceEnabled (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + std::string interface = ""; + bool enabled = false; + bool persist = false; + if ((parameters.HasLabel("interface")) && (parameters.HasLabel("enabled")) && (parameters.HasLabel("persist"))) + { + getStringParameter("interface", interface); + getBoolParameter("enabled", enabled); + getBoolParameter("persist", persist); + + IARM_BUS_NetSrvMgr_Iface_EventData_t iarmData = { 0 }; + strncpy(iarmData.enableInterface, interface.c_str(), INTERFACE_SIZE); + + // First set enabled state + iarmData.isInterfaceEnabled = enabled; + if (IARM_RESULT_SUCCESS == IARM_Bus_BroadcastEvent (IARM_BUS_NM_SRV_MGR_NAME, (IARM_EventId_t) IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_ENABLED, (void *)&iarmData, sizeof(iarmData))) + { + LOGINFO ("Broadcasted IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_ENABLED = %d for interface '%s'\n", iarmData.isInterfaceEnabled, iarmData.enableInterface); + + // Then set persist state + iarmData.isInterfaceEnabled = persist; + if (IARM_RESULT_SUCCESS == IARM_Bus_BroadcastEvent (IARM_BUS_NM_SRV_MGR_NAME, (IARM_EventId_t) IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_CONTROL_PERSISTENCE, (void *)&iarmData, sizeof(iarmData))) + { + LOGINFO("Broadcasted IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_CONTROL_PERSISTENCE = %d for interface '%s'\n", iarmData.isInterfaceEnabled, iarmData.enableInterface); + + response["success"] = true; + returnResponse(true); + } + else + { + LOGWARN ("Failed to broadcast IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_CONTROL_PERSISTENCE = %d for interface '%s'\n", iarmData.isInterfaceEnabled, iarmData.enableInterface); + } + } + else + { + LOGWARN ("Failed to broadcast IARM_BUS_NETWORK_MANAGER_EVENT_SET_INTERFACE_ENABLED = %d for interface '%s'\n", iarmData.isInterfaceEnabled, iarmData.enableInterface); + } + } + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::getNamedEndpoints(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + JsonArray namedEndpoints; + JsonObject endpoint; + + endpoint["endpoint"] = "CMTS"; + namedEndpoints.Add(endpoint); + + response["endpoints"] = namedEndpoints; + returnResponse(true); + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::trace(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + if (!parameters.HasLabel("endpoint")) + { + LOGERR("No endpoint specified"); + } + else + { + std::string endpoint = ""; + int packets = 0; + + getStringParameter("endpoint", endpoint); + if (parameters.HasLabel("packets")) // packets is optional? + { + getNumberParameter("packets", packets); + } + + if (_doTrace(endpoint, packets, response)) + { + returnResponse(true); + } + else + { + LOGERR("Failed to perform network trace"); + } + } + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::traceNamedEndpoint(const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + if (!parameters.HasLabel("endpointName")) + { + LOGERR("No endpointName specified"); + } + else + { + std::string endpointName = ""; + int packets = 0; + + getStringParameter("endpointName", endpointName); + if (parameters.HasLabel("packets")) // packets is optional? + { + getNumberParameter("packets", packets); + } + + if (_doTraceNamedEndpoint(endpointName, packets, response)) + { + returnResponse(true); + } + else + { + LOGERR("Failed to perform network trace names endpoint"); + } + } + } + else + { + LOGWARN ("This version of Network Software is not supporting this API..\n"); + } + + returnResponse(false); + } + + uint32_t Network::ping (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + std::string endpoint = ""; + uint32_t packets = DEFAULT_PING_PACKETS; + + if (parameters.HasLabel("packets")) + { + getNumberParameter("packets", packets); + } + + if (parameters.HasLabel("endpoint")) + { + getStringParameter("endpoint", endpoint); + response = _doPing(endpoint, packets); + + returnResponse(response["success"]); + } + else + { + // endpoint is required + returnResponse(false); + } + } + else + LOGWARN ("This version of Network Software is not supporting this API..\n"); + + returnResponse(false); + } + + uint32_t Network::pingNamedEndpoint (const JsonObject& parameters, JsonObject& response) + { + LOGWARN ("Entering %s \n", __FUNCTION__); + if (m_apiVersionNumber >= 1) + { + std::string endpoint = ""; + uint32_t packets = DEFAULT_PING_PACKETS; + + if (parameters.HasLabel("packets")) + { + getNumberParameter("packets", packets); + } + + if (parameters.HasLabel("endpointName")) + { + getStringParameter("endpointName", endpoint); + response = _doPingNamedEndpoint(endpoint, packets); + + returnResponse(response["success"]); + } + else + { + // endpoint is required + returnResponse(false); + } + } + else + LOGWARN ("This version of Network Software is not supporting this API..\n"); + + returnResponse(false); + } + + + /* + * Notifications + */ + void Network::_asyncNotifyConnection(JsonObject ¶ms) + { + sendNotify(EVT_NETWORK_CONNECTION_STATUS_UPDATE, params); + } + + void Network::_asyncNotifyIPAddr(JsonObject ¶ms) + { + sendNotify(EVT_NETWORK_CONNECTION_IP_ADDRESS_UPDATE, params); + } + + + /* + * Internal functions + */ + + bool Network::_getInterfaceEnabled(std::string &interface, bool &enabled) + { + IARM_BUS_NetSrvMgr_Iface_EventData_t param = {0}; + strncpy(param.enableInterface, interface.c_str(), INTERFACE_SIZE); + if (IARM_RESULT_SUCCESS == IARM_Bus_Call (IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_isInterfaceEnabled, (void*)¶m, sizeof(param))) + { + LOGINFO("%s :: Enabled = %d \n",__FUNCTION__,param.isInterfaceEnabled); + enabled = param.isInterfaceEnabled; + return true; + } + else + { + LOGWARN ("Call to %s for %s failed\n", IARM_BUS_NM_SRV_MGR_NAME, __FUNCTION__); + } + + return false; + } + + bool Network::_getActiveInterface(std::string &interface) + { + IARM_BUS_NetSrvMgr_Iface_EventData_t param; + if (IARM_RESULT_SUCCESS == IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_getActiveInterface, (void*)¶m, sizeof(param))) + { + LOGINFO("%s :: Interface = %s \n",__FUNCTION__,param.activeIface); + interface = param.activeIface; + return true; + } + else + { + LOGWARN ("Call to %s for %s failed\n", IARM_BUS_NM_SRV_MGR_NAME, __FUNCTION__); + } + + return false; + } + + /* Internal methods */ + bool Network::_getInterfaceMACAddress(std::string &interface, std::string &macAddr) + { + if (m_netUtils.GetInterfaceMACAddress(interface, macAddr)) + { + return true; + } + else + { + return false; + } + } + + bool Network::_getInterfaceConnected(std::string &interface, bool &connected) + { + if (m_netUtils.GetInterfaceConnected(interface, connected)) + { + return true; + } + else + { + return false; + } + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/Network.h b/Network/Network.h new file mode 100644 index 0000000000..7f38ad1d76 --- /dev/null +++ b/Network/Network.h @@ -0,0 +1,117 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include +#include +#include +#include + +#include "Module.h" +#include "NetUtils.h" +#include "utils.h" +#include "upnpdiscoverymanager.h" + + +// Define this to use netlink calls (where there may be an alternative method but netlink could provide +// the information or perform the action required) +//#define USE_NETLINK + +namespace WPEFramework { + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class Network : public PluginHost::IPlugin, public PluginHost::JSONRPC { + private: + + // We do not allow this plugin to be copied !! + Network(const Network&) = delete; + Network& operator=(const Network&) = delete; + + //Begin methods + uint32_t getQuirks(const JsonObject& parameters, JsonObject& response); + + // Network_API_Version_1 + uint32_t getInterfaces(const JsonObject& parameters, JsonObject& response); + uint32_t isInterfaceEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t setInterfaceEnabled(const JsonObject& parameters, JsonObject& response); + uint32_t getDefaultInterface(const JsonObject& parameters, JsonObject& response); + uint32_t setDefaultInterface(const JsonObject& parameters, JsonObject& response); + uint32_t getStbIp(const JsonObject& parameters, JsonObject& response); + uint32_t setApiVersionNumberWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getApiVersionNumberWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t trace(const JsonObject& parameters, JsonObject& response); + uint32_t traceNamedEndpoint(const JsonObject& parameters, JsonObject& response); + uint32_t getNamedEndpoints(const JsonObject& parameters, JsonObject& response); + uint32_t ping(const JsonObject& parameters, JsonObject& response); + uint32_t pingNamedEndpoint(const JsonObject& parameters, JsonObject& response); + + // Internal methods + bool _getInterfaceEnabled(std::string &interface, bool &enabled); + bool _getInterfaceMACAddress(std::string &interface, std::string &macAddr); + bool _getInterfaceConnected(std::string &interface, bool &connected); + + bool _doTrace(std::string &endpoint, int packets, JsonObject& response); + bool _doTraceNamedEndpoint(std::string &endpointName, int packets, JsonObject& response); + + JsonObject _doPing(std::string endPoint, int packets); + JsonObject _doPingNamedEndpoint(std::string endpointName, int packets); + + public: + Network(); + virtual ~Network(); + + //Build QueryInterface implementation, specifying all possible interfaces to be returned. + BEGIN_INTERFACE_MAP(Network) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP + + void _asyncNotifyConnection(JsonObject ¶ms); + void _asyncNotifyIPAddr(JsonObject ¶ms); + void _asyncNotifyTrace(JsonObject ¶ms); + bool _getActiveInterface(std::string &interface); + + //IPlugin methods + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override; + + public: + static Network *_instance; + static Network *getInstance() {return _instance;} + + private: + uint32_t m_apiVersionNumber; + NetUtils m_netUtils; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/NetworkTraceroute.cpp b/Network/NetworkTraceroute.cpp new file mode 100644 index 0000000000..52ae018e7f --- /dev/null +++ b/Network/NetworkTraceroute.cpp @@ -0,0 +1,134 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Network.h" +#include +#include + +//e.g. traceroute -w 3 -m 6 -q 3 192.168.1.100 52 +#define CMD_TRACEROUTE "traceroute -w %d -m %d -q %d %s %d 2>&1" +//e.g. traceroute6 -i eth0 -w 3 -m 6 -q 3 fe80::5a19:f8ff:fe37:7a3d 52 +#define CMD_TRACEROUTE6 "traceroute6 -i %s -w %d -m %d -q %d %s %d 2>&1" + +#define DEFAULT_PACKET_LENGTH 52 +#define DEFAULT_WAIT 3 +#define DEFAULT_MAX_HOPS 6 +#define DEFAULT_QUERIES 3 + + +namespace WPEFramework { + namespace Plugin { + + bool Network::_doTraceNamedEndpoint(std::string &endpointName, int packets, JsonObject &response) + { + std::string endpoint = ""; + + if (endpointName != "CMTS") // currently we only support CMTS + { + response["error"] = "Unsupported named endpoint"; + } + else if (m_netUtils.getCMTSGateway(endpoint)) + { + return _doTrace(endpoint, packets, response); + } + else + { + response["error"] = "Could not find CMTS gateway"; + } + + return false; + } + + bool Network::_doTrace(std::string &endpoint, int packets, JsonObject &response) + { + std::string output = ""; + std::string error = ""; + std::string interface = ""; + int wait = DEFAULT_WAIT; + int maxHops = DEFAULT_MAX_HOPS; + int packetLen = DEFAULT_PACKET_LENGTH; + char command[MAX_COMMAND_LENGTH]; + bool result = false; + + if (packets <= 0) + { + packets = DEFAULT_QUERIES; + } + + if (endpoint.empty()) + { + error = "Invalid endpoint"; + } + else if (!NetUtils::isIPV4(endpoint) && !NetUtils::isIPV6(endpoint)) + { + error = "Invalid endpoint"; + } + else if (!m_netUtils.getCMTSInterface(interface)) + { + error = "Could not get default interface"; + } + else + { + if (NetUtils::isIPV6(endpoint)) + { + snprintf(command, MAX_COMMAND_LENGTH, CMD_TRACEROUTE6, + interface.c_str(), + wait, + maxHops, + packets, + endpoint.c_str(), + packetLen); + } + else + { + snprintf(command, MAX_COMMAND_LENGTH, CMD_TRACEROUTE, + wait, + maxHops, + packets, + endpoint.c_str(), + packetLen); + } + + if (NetUtils::execCmd(command, output, &result) < 0) + { + error = "Failed to execute traceroute command"; + } + else if (!result) // check the command return status + { + error = "Failed to execute traceroute"; + } + } + + if (error.empty()) + { + response["target"] = endpoint; + response["results"] = output; + response["error"] = ""; + return true; + } + else + { + response["target"] = endpoint; + response["results"] = ""; + response["error"] = error; + return false; + } + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/Network/PingNotifier.cpp b/Network/PingNotifier.cpp new file mode 100644 index 0000000000..eead9789fb --- /dev/null +++ b/Network/PingNotifier.cpp @@ -0,0 +1,226 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Network.h" + +namespace WPEFramework +{ + namespace Plugin + { + // Not every character can be used for endpoint + bool is_character_illegal(const int& c) + { + //character must be "-./0-9a-zA-Z" + return (c < 45) || ((c > 58) && (c < 97)) || (c >= 122); + } + + // Check if valid - consist of only allowed characters + bool is_endpoint_valid(const std::string& endpoint) + { + //return std::find_if(endpoint.begin(), endpoint.end(), is_character_illegal) == endpoint.end(); + return (NetUtils::isIPV4(endpoint) || NetUtils::isIPV6(endpoint)); + } + + /** + * @ingroup SERVMGR_PING_API + */ + JsonObject Network::_doPing(std::string endPoint, int packets) + { + LOGINFO("PingService calling ping"); + JsonObject pingResult; + std::string interface = ""; + bool result = false; + std::string outputFile; + FILE *fp = NULL; + + pingResult["target"] = endPoint; + + if(!is_endpoint_valid(endPoint)) + { + LOGERR("%s: Endpoint is not valid string", __FUNCTION__); + pingResult["success"] = false; + pingResult["error"] = "invalid input for endpoint: " + endPoint; + return pingResult; + } + + if (!m_netUtils.getCMTSInterface(interface)) + { + LOGERR("%s: Could not get default interface", __FUNCTION__); + pingResult["success"] = false; + pingResult["error"] = "Could not get default interface"; + return pingResult; + } + + char cmd [1000] = {0x0}; + if (NetUtils::isIPV6(endPoint)) + { + snprintf(cmd, sizeof(cmd), "ping6 -I %s -c %d -W 5 %s 2>&1", + interface.c_str(), packets, endPoint.c_str()); + } + else + { + snprintf(cmd, sizeof(cmd), "ping -c %d -W 5 %s 2>&1", + packets, endPoint.c_str()); + } + + LOGWARN("ping command: %s", cmd); + + // Run the command and dump the output to /tmp/pingoutput + if (NetUtils::execCmd(cmd, outputFile, &result, "pingoutput") < 0) + { + LOGERR("%s: SERVICEMANAGER_FILE_ERROR: Can't open pipe for command '%s' for read mode: %s" + , __FUNCTION__, cmd, strerror(errno)); + + pingResult["success"] = false; + pingResult["error"] = "Could not run command"; + } + else if (!result) // check the command return status + { + pingResult["success"] = false; + pingResult["error"] = "Could not ping endpoint"; + } + else if ((fp = fopen(outputFile.c_str(), "r")) == NULL) + { + pingResult["success"] = false; + pingResult["error"] = "Could not read ping result"; + } + else + { + pingResult["success"] = true; + pingResult["error"] = ""; + + char linearray[1000]={0x0}; + while(fgets(linearray, sizeof(linearray), fp) != NULL) + { + std::string line(linearray); + LOGINFO("ping result: %s", line.c_str()); + + if( line.find( "packet" ) != std::string::npos ) { + //Example: 10 packets transmitted, 10 packets received, 0% packet loss + + std::stringstream ss( line ); + int transCount; + ss >> transCount; + pingResult["packetsTransmitted"] = transCount; + + std::string token; + getline( ss, token, ',' ); + getline( ss, token, ',' ); + std::stringstream ss2( token ); + int rxCount; + ss2 >> rxCount; + pingResult["packetsReceived"] = rxCount; + + getline( ss, token, ',' ); + std::string prefix = token.substr(0, token.find("%")); + //double lossFloat = ::atof(prefix.c_str()); + //pingResult["packetLoss"] = lossFloat; + pingResult["packetLoss"] = prefix.c_str(); + + }else if( line.find( "min/avg/max" ) != std::string::npos ) { + //Example: round-trip min/avg/max = 17.038/18.310/20.197 ms + + std::stringstream ss( line ); + std::string fullpath; + getline( ss, fullpath, '=' ); + getline( ss, fullpath, '=' ); + + std::string prefix; + int index = fullpath.find("/"); + if (index >= 0) + { + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripMin"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripAvg"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + prefix = fullpath.substr(0, fullpath.find("/")); + pingResult["tripMax"] = prefix.c_str(); + } + + index = fullpath.find("/"); + if (index >= 0) + { + fullpath = fullpath.substr(index + 1, fullpath.length()); + pingResult["tripStdDev"] = prefix.c_str(); + } + }else if( line.find( "bad" ) != std::string::npos ) { + pingResult["success"] = false; + pingResult["error"] = "Bad Address"; + } + } + fclose(fp); + + // clear up + std::remove(outputFile.c_str()); + } + + return pingResult; + } + + /** + * @ingroup SERVMGR_PING_API + */ + JsonObject Network::_doPingNamedEndpoint(std::string endpointName, int packets) + { + LOGINFO("PingService calling pingNamedEndpoint for %s", endpointName.c_str()); + std::string error = ""; + JsonObject returnResult; + + if (endpointName == "CMTS") + { + std::string gateway; + if (m_netUtils.getCMTSGateway(gateway)) + { + returnResult = _doPing(gateway, packets); + } + else + { + LOGERR("%s: Can't get gateway address for interface CMTS", __FUNCTION__); + error = "Could not find interface"; + } + } + else + { + error = "Invalid endpoint name"; + } + + + if (error != "") + { + returnResult["target"] = endpointName; + returnResult["success"] = false; + returnResult["error"] = error; + } + + return returnResult; + } + } +} diff --git a/Network/README.md b/Network/README.md new file mode 100644 index 0000000000..df26c88e0f --- /dev/null +++ b/Network/README.md @@ -0,0 +1,35 @@ +----------------- +Build: + +bitbake thunder-plugins +----------------- + +Test: + +Commands to use +---------------- +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getQuirks"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setApiVersionNumber", "params":{"version":5}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getInterfaces"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getDefaultInterface"}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setDefaultInterface", "params":{"interface":"WIFI", "persist":false}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.isInterfaceEnabled", "params":{"interface":"WIFI"}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.setInterfaceEnabled", "params":{"interface":"WIFI", "enabled":true, "persist":true}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getStbIp"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.getNamedEndpoints"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.trace", "params":{"endpoint":"45.57.221.20", "packets": 3}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.traceNamedEndpoint", "params":{"endpointName":"CMTS", "packets": 3}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.ping", "params":{"endpoint":"45.57.221.20", "packets": 3}}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","method": "org.rdk.Network.1.pingNamedEndpoint", "params":{"endpointName":"CMTS", "packets": 3}}' http://127.0.0.1:9998/jsonrpc + + + diff --git a/RemoteActionMapping/CMakeLists.txt b/RemoteActionMapping/CMakeLists.txt new file mode 100644 index 0000000000..cdce31e7fd --- /dev/null +++ b/RemoteActionMapping/CMakeLists.txt @@ -0,0 +1,53 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME RemoteActionMapping) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_subdirectory(test) + +add_library(${MODULE_NAME} SHARED + RemoteActionMapping.cpp + RamHelper.cpp + Module.cpp + ../helpers/utils.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +find_package(CTRLM) +if (CTRLM_FOUND) + add_definitions(-DCTRLM_FOUND) + target_include_directories(${MODULE_NAME} PRIVATE ${CTRLM_INCLUDE_DIRS}) + find_package(IARMBus) + target_include_directories(${MODULE_NAME} PRIVATE ${IARMBUS_INCLUDE_DIRS}) + find_package(IRMGR) + target_include_directories(${MODULE_NAME} PRIVATE ${IRMGR_INCLUDE_DIRS}) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins ${IARMBUS_LIBRARIES}) +else (CTRLM_FOUND) + target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins) +endif(CTRLM_FOUND) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/RemoteActionMapping/Module.cpp b/RemoteActionMapping/Module.cpp new file mode 100644 index 0000000000..ce759b615f --- /dev/null +++ b/RemoteActionMapping/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/RemoteActionMapping/Module.h b/RemoteActionMapping/Module.h new file mode 100644 index 0000000000..5c409c6dfd --- /dev/null +++ b/RemoteActionMapping/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME RemoteActionMapping +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/RemoteActionMapping/README.md b/RemoteActionMapping/README.md new file mode 100644 index 0000000000..bcb835e41b --- /dev/null +++ b/RemoteActionMapping/README.md @@ -0,0 +1,55 @@ +----------------- +Build: + +bitbake thunder-plugins + +----------------- +Test: + + +Methods common to typical plugins - getQuirks() and getApiVersionNumber() (not documented in official RemoteActionMapping API) +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "org.rdk.RemoteActionMapping.1.getQuirks"}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"4","method":"org.rdk.RemoteActionMapping.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc + + +An example getLastUsedDeviceID() call (takes no arguments) +curl -d '{"jsonrpc":"2.0","id":"5","method":"org.rdk.RemoteActionMapping.1.getLastUsedDeviceID"}' http://127.0.0.1:9998/jsonrpc + + +An example getKeymap() method call (takes 'deviceID' and 'keymapType' parameters) +curl -d '{"jsonrpc":"2.0","id":"6","method":"org.rdk.RemoteActionMapping.1.getKeymap","params": {"deviceID":2, "keymapType":0}}' http://127.0.0.1:9998/jsonrpc + + +An example setKeyActionMapping() method call (takes 'deviceID' and 'keymapType' integer parameters, and a 'keyActionMapping' array of keyActionMap objects, which contain IR waveform data, among other things). This example is for Samsung TV only (no AVR data). +curl -d '{"jsonrpc":"2.0","id":"7","method":"org.rdk.RemoteActionMapping.1.setKeyActionMapping","params": {"deviceID":1, "keymapType":0, "keyActionMapping":[{"keyName":80,"rfKeyCode":109,"tvIRKeyCode":[4,19,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,50,51,34,51,35,34,51,34,48],"avrIRKeyCode":[]},{"keyName":81,"rfKeyCode":108,"tvIRKeyCode":[4,19,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,51,51,34,51,34,34,51,34,48],"avrIRKeyCode":[]},{"keyName":128,"rfKeyCode":107,"tvIRKeyCode":[4,19,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,51,35,51,51,50,50,34,34,32],"avrIRKeyCode":[]},{"keyName":138,"rfKeyCode":65,"tvIRKeyCode":[4,17,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,50,34,51,51,51,51,34,34,32],"avrIRKeyCode":[]},{"keyName":139,"rfKeyCode":66,"tvIRKeyCode":[4,17,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,50,35,35,51,51,50,50,34,32],"avrIRKeyCode":[]},{"keyName":140,"rfKeyCode":67,"tvIRKeyCode":[4,17,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,50,34,35,51,51,51,50,34,32],"avrIRKeyCode":[]},{"keyName":208,"rfKeyCode":52,"tvIRKeyCode":[4,17,4,0,34,0,210,0,144,0,4,45,101,4,101,4,144,0,166,1,144,0,141,0,18,34,51,51,50,34,51,51,50,51,51,51,51,34,34,34,32],"avrIRKeyCode":[]}]}}' http://127.0.0.1:9998/jsonrpc + + +An example setFiveDigitCode() method call (takes 'deviceID', 'tvFiveDigitCode', and 'avrFiveDigitCode' integer parameters) +curl -d '{"jsonrpc":"2.0","id":"8","method":"org.rdk.RemoteActionMapping.1.setFiveDigitCode","params": {"deviceID":1, "tvFiveDigitCode":12051, "avrFiveDigitCode":32610}}' http://127.0.0.1:9998/jsonrpc + +Another setFiveDigitCode() example - this one clears both the TV and AVR 5-digit-codes, if they are set +curl -d '{"jsonrpc":"2.0","id":"8","method":"org.rdk.RemoteActionMapping.1.setFiveDigitCode","params": {"deviceID":1, "tvFiveDigitCode":0, "avrFiveDigitCode":0}}' http://127.0.0.1:9998/jsonrpc + +Another setFiveDigitCode() example - this one sets the AVR 5-digit-code, but leaves any existing TV 5-digit-code untouched +curl -d '{"jsonrpc":"2.0","id":"8","method":"org.rdk.RemoteActionMapping.1.setFiveDigitCode","params": {"deviceID":1, "tvFiveDigitCode":0, "avrFiveDigitCode":32610}}' http://127.0.0.1:9998/jsonrpc + + +An example clearKeyActionMapping() method call (takes 'deviceID' and 'keymapType', integer parameters, and a 'keyNames' byte array of KED key values, to indicate which database slots to clear). The example below clears them all (the most reasonable choice). +curl -d '{"jsonrpc":"2.0","id":"9","method":"org.rdk.RemoteActionMapping.1.clearKeyActionMapping","params": {"deviceID":1, "keymapType":0, "keyNames":[128,81,80,138,139,140,208]}}' http://127.0.0.1:9998/jsonrpc + +curl -d '{"jsonrpc":"2.0","id":"9","method":"org.rdk.RemoteActionMapping.1.clearKeyActionMapping","params": {"deviceID":1, "keymapType":0, "keyNames":[0x80,0x51,0x50,0x8A,0x8B,0x8C,0xD0]}}' http://127.0.0.1:9998/jsonrpc + + +An example getFullKeyActionMapping() method call (takes 'deviceID' and 'keymapType' integer parameters). +curl -d '{"jsonrpc":"2.0","id":"10","method":"org.rdk.RemoteActionMapping.1.getFullKeyActionMapping","params": {"deviceID":1, "keymapType":0}}' http://127.0.0.1:9998/jsonrpc + + +An example getSingleKeyActionMapping() method call (takes 'deviceID', 'keymapType', and 'keyName' integer parameters). KED_MUTE used here. +curl -d '{"jsonrpc":"2.0","id":"11","method":"org.rdk.RemoteActionMapping.1.getSingleKeyActionMapping","params": {"deviceID":1, "keymapType":0, "keyName":140}}' http://127.0.0.1:9998/jsonrpc + + +An example cancelCodeDownload() method call (takes 'deviceID' integer parameter). +curl -d '{"jsonrpc":"2.0","id":"12","method":"org.rdk.RemoteActionMapping.1.cancelCodeDownload","params": {"deviceID":2}}' http://127.0.0.1:9998/jsonrpc + + diff --git a/RemoteActionMapping/RamHelper.cpp b/RemoteActionMapping/RamHelper.cpp new file mode 100644 index 0000000000..4d98c40d23 --- /dev/null +++ b/RemoteActionMapping/RamHelper.cpp @@ -0,0 +1,1827 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "RamHelper.h" +#include "utils.h" + +// IR-RF Database RF descriptors, needed for all original configurable keys +// Discrete Power ON/OFF use actual RF keycodes (0x6D, 0x6C), the rest are all XRC ghost codes +unsigned char const rfDescriptor_DiscretePwrOn[] = { 0x01, 0x4C, 0x02, 0x01, 0x6D }; +unsigned char const rfDescriptor_DiscretePwrOff[] = { 0x01, 0x4C, 0x02, 0x01, 0x6C }; + +unsigned char const rfDescriptor_IRPowerToggle[] = { 0x21, 0x4C, 0x02, 0x31, 0x03 }; +unsigned char const rfDescriptor_VolumeUp[] = { 0x01, 0x4C, 0x02, 0x31, 0x06 }; +unsigned char const rfDescriptor_VolumeDown[] = { 0x01, 0x4C, 0x02, 0x31, 0x07 }; +unsigned char const rfDescriptor_Mute[] = { 0x01, 0x4C, 0x02, 0x31, 0x08 }; +unsigned char const rfDescriptor_Input[] = { 0x01, 0x4C, 0x02, 0x31, 0x09 }; + +// The following symbol enables code that swaps the power-related Device Type field values, +// in the unusual case where an AVR has the toggle power slot, and a TV has the discrete slots. +// This is done in order to get the XR11 All Power button to work properly, in this special case. +#define SPECIAL_XR11_POWER_DEVICETYPE_SWAP_ENABLE 1 + +#if CONTROLMGR_MAX_IR_DATA_SIZE > (CTRLM_RCU_MAX_RIB_ATTRIBUTE_SIZE - 8) // Eight is flag(1)+RFghost(5)+IRheader(2) +#error "ControlMgr RIB request data size is too small for max IR codes!!" +#endif + +using namespace std; + +namespace WPEFramework { + + namespace Plugin { + + // + // IARM-level RemoteActionMappingHelper Methods + // + + ctrlm_network_id_t RemoteActionMappingHelper::getRf4ceNetworkID() + { + ctrlm_main_iarm_call_status_t status; + ctrlm_network_id_t rf4ceId = CTRLM_MAIN_NETWORK_ID_INVALID; + IARM_Result_t res; + + memset((void*)&status, 0, sizeof(status)); + status.api_revision = CTRLM_MAIN_IARM_BUS_API_REVISION; + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_MAIN_IARM_CALL_STATUS_GET, (void*)&status, sizeof(status)); + if (res != IARM_RESULT_SUCCESS) + { + LOGERR("ERROR - STATUS_GET IARM_Bus_Call FAILED, res: %d", (int)res); + } + else + { + if (status.result != CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGERR("ERROR - STATUS_GET FAILED, call_result: %d", (int)status.result); + } + else + { + // Apparent success, search for the RF4CE network ID. + for (int i = 0; i < status.network_qty; i++) + { + if (status.networks[i].type == CTRLM_NETWORK_TYPE_RF4CE) + { + rf4ceId = status.networks[i].id; + break; + } + } + } + } + + return rf4ceId; + } + + bool RemoteActionMappingHelper::getRf4ceBindRemotes(rf4ceBindRemotes_t* bindRemotes) + { + ctrlm_main_iarm_call_network_status_t netStatus; + ctrlm_rcu_iarm_call_controller_status_t ctrlStatus; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + bool retVal = false; + bool found = false; + int ctrlCount = 0; + + if (bindRemotes == NULL) + { + LOGERR("LOGIC ERROR - no bindRemotes output array passed in!!"); + return false; + } + memset((void*)bindRemotes, 0, sizeof(rf4ceBindRemotes_t)); + + // Get the status of all the paired remotes on the rf4ce network. + // Start by finding the network_id of the rf4ce network on this STB. + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId != CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGWARN("found rf4ce network_id: %d.", (int)rf4ceId); + } + else + { + LOGWARN("WARNING - No RF4CE network_id found!"); + return false; + } + + // Next, get the array of controller IDs for the rf4ce network. + memset((void*)&netStatus, 0, sizeof(netStatus)); + netStatus.api_revision = CTRLM_MAIN_IARM_BUS_API_REVISION; + netStatus.network_id = rf4ceId; + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_MAIN_IARM_CALL_NETWORK_STATUS_GET, (void*)&netStatus, sizeof(netStatus)); + if (res != IARM_RESULT_SUCCESS) + { + LOGERR("ERROR - NETWORK_STATUS_GET IARM_Bus_Call FAILED, res: %d", (int)res); + return false; + } + else + { + if (netStatus.result != CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGERR("ERROR - NETWORK_STATUS_GET FAILED, call_result: %d", (int)netStatus.result); + return false; + } + } + LOGWARN("status_rf4ce - version_hal: %s, controller_qty: %d, pan_id: 0x%04X, rf_channel number: 0x%02X, rf_channel quality: 0x%02X.\n", + netStatus.status.rf4ce.version_hal, netStatus.status.rf4ce.controller_qty, netStatus.status.rf4ce.pan_id, + netStatus.status.rf4ce.rf_channel_active.number, netStatus.status.rf4ce.rf_channel_active.quality); + + if (netStatus.status.rf4ce.controller_qty == 0) + { + LOGWARN("WARNING - No RF4CE controllers found!"); + return false; + } + + // There are one or more controllers paired on the rf4ce network. + // Get the status for each one, and put them in the bindRemotes array, in last-used order. + retVal = false; + for (int i = 0; i < netStatus.status.rf4ce.controller_qty; i++) + { + memset((void*)&ctrlStatus, 0, sizeof(ctrlStatus)); + ctrlStatus.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ctrlStatus.network_id = rf4ceId; + ctrlStatus.controller_id = netStatus.status.rf4ce.controllers[i]; + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_CONTROLLER_STATUS, (void*)&ctrlStatus, sizeof(ctrlStatus)); + if (res != IARM_RESULT_SUCCESS) + { + LOGERR("ERROR - CONTROLLER_STATUS IARM_Bus_Call FAILED, res: %d, controller_id: %d, index: %d", + (int)res, (int)netStatus.status.rf4ce.controllers[i], i); + } + else + { + if (ctrlStatus.result != CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGERR("ERROR - CONTROLLER_STATUS FAILED, call_result: %d, controller_id: %d, index: %d", + (int)ctrlStatus.result, (int)netStatus.status.rf4ce.controllers[i], i); + } + else + { + int insIdx = 0; + + LOGWARN("controller_status[%d] - controller_id: %d, type: %s, SW version: %s, HW version: %s, time_last_key: %lu.", + i, netStatus.status.rf4ce.controllers[i], ctrlStatus.status.type, + ctrlStatus.status.version_software, ctrlStatus.status.version_hardware, ctrlStatus.status.time_last_key); + + if (ctrlCount > CTRLM_MAIN_MAX_BOUND_CONTROLLERS) + { + LOGERR("LOGIC ERROR - Limit of MAX_BOUND_CONTROLLERS exceeded, controller_id: %d, index: %d!!!", + (int)netStatus.status.rf4ce.controllers[i], i); + break; + } + found = false; + // Insert this controller_status in the output, in most-recently-used order. + for (insIdx = 0; insIdx < CTRLM_MAIN_MAX_BOUND_CONTROLLERS; insIdx++) + { + // Search for the insertion point + if (bindRemotes->remotes[insIdx].status.time_last_key < ctrlStatus.status.time_last_key) + { + // Make room for the insertion, if needed + // ctrlCount can also be considered the "first-empty-slot-index". + if (bindRemotes->remotes[insIdx].status.time_last_key != 0) + { + int slots2move = ctrlCount - insIdx; + controller_info* src = &(bindRemotes->remotes[insIdx]); + controller_info* dest = src + 1; + memmove((void*)dest, (void*)src, (slots2move * sizeof(controller_info))); + } + // Insert the controller_status + bindRemotes->remotes[insIdx].network_id = rf4ceId; + bindRemotes->remotes[insIdx].controller_id = netStatus.status.rf4ce.controllers[i]; + bindRemotes->remotes[insIdx].status = ctrlStatus.status; + found = true; + break; + } + } + if (found) + { + ctrlCount++; + bindRemotes->numBindRemotes = ctrlCount; + retVal = true; + LOGWARN("controller_status[%d] - controller_id: %d, has been inserted at index %d.", + i, netStatus.status.rf4ce.controllers[i], insIdx); + } + else + { + LOGERR("LOGIC ERROR - No slot found to insert controller_status[%d] - controller_id: %d!!!", + i, netStatus.status.rf4ce.controllers[i]); + } + } + } + } + + return retVal; + } + + int RemoteActionMappingHelper::getLastUsedDeviceID(std::string& remoteType, bool& bFiveDigitCodeSet, bool& bFiveDigitCodeSupported) + { + rf4ceBindRemotes_t bindRemotes; + int deviceID = -1; + + if (getRf4ceBindRemotes(&bindRemotes)) + { + LOGWARN("controlMgr: %d RF4CE remotes found.", bindRemotes.numBindRemotes); + + for (int i = 0; i < bindRemotes.numBindRemotes; i++) + { + LOGWARN("controller_id: %d, type: %s, firmware: %s, hardware: %s, battery: %.2f volts, time_last_key: %lu.", + bindRemotes.remotes[i].controller_id, bindRemotes.remotes[i].status.type, + bindRemotes.remotes[i].status.version_software, bindRemotes.remotes[i].status.version_hardware, + bindRemotes.remotes[i].status.battery_voltage_loaded, bindRemotes.remotes[i].status.time_last_key); + } + + deviceID = bindRemotes.remotes[0].controller_id; + remoteType = std::string(bindRemotes.remotes[0].status.type); + + // Set the booleans concerning 5-digit codes. + bFiveDigitCodeSet = (bindRemotes.remotes[0].status.ir_db_state == CTRLM_RCU_IR_DB_STATE_TV_CODE) || + (bindRemotes.remotes[0].status.ir_db_state == CTRLM_RCU_IR_DB_STATE_AVR_CODE) || + (bindRemotes.remotes[0].status.ir_db_state == CTRLM_RCU_IR_DB_STATE_TV_AVR_CODES); + + #if (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + bFiveDigitCodeSupported = (bindRemotes.remotes[0].status.ir_db_code_download_supported > 0); + #else + bFiveDigitCodeSupported = false; + #endif // (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + } + else + { + LOGWARN("NO valid RF4CE remotes found!"); + } + + return deviceID; + } + + bool RemoteActionMappingHelper::getControllerByID(int deviceID, std::string& remoteType, bool& bFiveDigitCodeSet, bool& bFiveDigitCodeSupported) + { + ctrlm_rcu_iarm_call_controller_status_t ctrlStatus; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + + // Find the network_id of the rf4ce network on this STB. + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId != CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGWARN("found rf4ce network_id: %d.", (int)rf4ceId); + } + else + { + LOGWARN("WARNING - No RF4CE network_id found!"); + return false; + } + + // Get the controller status + memset((void*)&ctrlStatus, 0, sizeof(ctrlStatus)); + ctrlStatus.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ctrlStatus.network_id = rf4ceId; + ctrlStatus.controller_id = deviceID; + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_CONTROLLER_STATUS, (void*)&ctrlStatus, sizeof(ctrlStatus)); + if (res != IARM_RESULT_SUCCESS) + { + LOGERR("CONTROLLER_STATUS IARM_Bus_Call FAILED, res: %d, controller_id: %d, network_id: %d.", + (int)res, deviceID, rf4ceId); + return false; + } + else + { + if (ctrlStatus.result != CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGERR("CONTROLLER_STATUS FAILED, call_result: %d, controller_id: %d, network_id: %d.", + (int)ctrlStatus.result, deviceID, rf4ceId); + return false; + } + else + { + LOGWARN("controller_id: %d, type: %s, firmware: %s, hardware: %s, ir_db_state: %d, ir_db_code_download_supported: %s.", + ctrlStatus.controller_id, ctrlStatus.status.type, + ctrlStatus.status.version_software, ctrlStatus.status.version_hardware, + #if (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + ctrlStatus.status.ir_db_state, (ctrlStatus.status.ir_db_code_download_supported ? "TRUE" : "FALSE")); + #else + ctrlStatus.status.ir_db_state, "FALSE")); + #endif // (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + + remoteType = std::string(ctrlStatus.status.type); + + // Set the booleans concerning 5-digit codes. + bFiveDigitCodeSet = (ctrlStatus.status.ir_db_state == CTRLM_RCU_IR_DB_STATE_TV_CODE) || + (ctrlStatus.status.ir_db_state == CTRLM_RCU_IR_DB_STATE_AVR_CODE) || + (ctrlStatus.status.ir_db_state == CTRLM_RCU_IR_DB_STATE_TV_AVR_CODES); + + #if (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + bFiveDigitCodeSupported = (ctrlStatus.status.ir_db_code_download_supported > 0); + #else + bFiveDigitCodeSupported = false; + #endif // (CTRLM_RCU_IARM_BUS_API_REVISION >= 4) + } + } + + return true; + } + + // All actionMap members must be set properly + bool RemoteActionMappingHelper::setKeyActionMap(int deviceID, int keymapType, + keyActionMap& actionMap, + const KeyGroupSrcInfo& srcInfo) + { + UNUSED(keymapType); + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + int deviceType = -1; // 0 is TV, 1 is AVR, -1 is none. + int dataSize = 0; + unsigned char* data = NULL; + unsigned char flags = MSO_RIB_IRRFDB_PERMANENT_BIT | MSO_RIB_IRRFDB_IRSPECIFIED_BIT; + unsigned char irConfig = 0; + unsigned char* bytePtr = (unsigned char*)&(ribRequest.data[0]); + unsigned char* rfDesc = NULL; + size_t rfDescLength = 0; + size_t total = 0; + + if ((deviceID < 1) || (actionMap.keyName < 0x30) || + (actionMap.rfKeyCode < 0) || (actionMap.rfKeyCode > 255) || + ((actionMap.tvIRData.size() <= 1) && (actionMap.avrIRData.size() <= 1))) + { + LOGERR("ERROR - Bad arguments! Cannot set map!"); + return false; + } + + if (actionMap.keyName == KED_UNDEFINEDKEY) + { + LOGWARN("WARNING - actionMap for rfKeyCode 0x%02X is an alternate!", actionMap.rfKeyCode); + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Cannot set map!"); + return false; + } + + // Use the srcInfo to select the correct IR data source, for the current RF key. + switch (actionMap.rfKeyCode) + { + // INPUT is a group all by itself + case MSO_RFKEY_INPUT_SELECT: + if (srcInfo.groupInputSelect == KEY_GROUP_SRC_TV) + { + // Use the TV blob + LOGWARN("Setting TV IR code for INPUT_SELECT."); + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupInputSelect == KEY_GROUP_SRC_AVR) + { + // Use the AVR blob + LOGWARN("Setting AVR IR code for INPUT_SELECT."); + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else + { + LOGWARN("WARNING - No INPUT_SELECT, cannot set IRRFDB slot, src: %d!", srcInfo.groupInputSelect); + } + break; + // POWER_TOGGLE is a group all by itself + case MSO_RFKEY_PWR_TOGGLE: + if (srcInfo.groupTogglePower == KEY_GROUP_SRC_TV) + { + // Use the TV blob + LOGWARN("Setting TV IR code for POWER_TOGGLE."); + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupTogglePower == KEY_GROUP_SRC_AVR) + { + // Use the AVR blob + LOGWARN("Setting AVR IR code for POWER_TOGGLE."); + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else + { + LOGWARN("WARNING - No POWER_TOGGLE, cannot set IRRFDB slot, src: %d!", srcInfo.groupTogglePower); + } + break; + case MSO_RFKEY_PWR_OFF: + case MSO_RFKEY_PWR_ON: + { + const char *name = (actionMap.rfKeyCode == MSO_RFKEY_PWR_ON) ? "DISCRETE_PWR_ON" : "DISCRETE_PWR_OFF"; + if (srcInfo.groupDiscretePower == KEY_GROUP_SRC_TV) + { + // Use the TV blob + LOGWARN("Setting TV IR code for %s.", name); + irConfig = 0x4F; // Tweak database, min transmissions = 0xF + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupDiscretePower == KEY_GROUP_SRC_AVR) + { + // Use the AVR blob + LOGWARN("Setting AVR IR code for %s.", name); + irConfig = 0x4F; // Tweak database, min transmissions = 0xF + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else if (srcInfo.groupDiscretePower == KEY_GROUP_SRC_TV_PWR_CROSS) + { + // Use the TV blob + LOGWARN("Setting TV toggle IR code into %s.", name); + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupDiscretePower == KEY_GROUP_SRC_AVR_PWR_CROSS) + { + // Use the AVR blob + LOGWARN("Setting AVR toggle IR code into %s.", name); + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else + { + LOGWARN("WARNING - No %s, cannot set IRRFDB slot, src: %d!", name, srcInfo.groupDiscretePower); + } + break; + } + case MSO_RFKEY_VOL_PLUS: + case MSO_RFKEY_VOL_MINUS: + { + const char *name = (actionMap.rfKeyCode == MSO_RFKEY_VOL_PLUS) ? "VOLUME_UP" : "VOLUME_DOWN"; + if (srcInfo.groupVolume == KEY_GROUP_SRC_TV) + { + // Use the TV blob + LOGWARN("Setting TV IR code for %s.", name); + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupVolume == KEY_GROUP_SRC_AVR) + { + // Use the AVR blob + LOGWARN("Setting AVR IR code for %s.", name); + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else + { + LOGWARN("WARNING - No %s, cannot set IRRFDB slot, src: %d!", name, srcInfo.groupVolume); + } + break; + } + case MSO_RFKEY_MUTE: + { + if (srcInfo.groupMute == KEY_GROUP_SRC_TV) + { + // Use the TV blob + LOGWARN("Setting TV IR code for MUTE."); + data = actionMap.tvIRData.data(); + dataSize = actionMap.tvIRData.size(); + deviceType = 0; + } + else if (srcInfo.groupMute == KEY_GROUP_SRC_AVR) + { + // Use the AVR blob + LOGWARN("Setting AVR IR code for MUTE."); + data = actionMap.avrIRData.data(); + dataSize = actionMap.avrIRData.size(); + deviceType = 1; + } + else + { + LOGWARN("WARNING - No MUTE, cannot set IRRFDB slot, src: %d!", srcInfo.groupMute); + } + break; + } + default: + LOGERR("LOGIC ERROR - RF Key 0x%02X is outside the map!", (unsigned)actionMap.rfKeyCode); + return false; + } + + if (dataSize > CONTROLMGR_MAX_IR_DATA_SIZE) + { + LOGERR("LOGIC ERROR - IRCode dataSize %d, for RF Key 0x%02X, exceeds size limits!", + dataSize, (unsigned)actionMap.rfKeyCode); + return false; + } + + if ((deviceType < 0) || (dataSize == 0) || (data == NULL)) + { + // Neither TV nor AVR IR code is available for this RF key. This indicates missing actionMap data, a fatal error! + LOGERR("LOGIC ERROR - No TV or AVR IR code available for RF Key: 0x%02X!!\n", (unsigned)actionMap.rfKeyCode); + return false; + } + + #ifdef SPECIAL_XR11_POWER_DEVICETYPE_SWAP_ENABLE + if ((srcInfo.groupTogglePower == KEY_GROUP_SRC_AVR) && + (srcInfo.groupDiscretePower == KEY_GROUP_SRC_TV) && + ((actionMap.rfKeyCode == MSO_RFKEY_PWR_TOGGLE) || + (actionMap.rfKeyCode == MSO_RFKEY_PWR_OFF) || + (actionMap.rfKeyCode == MSO_RFKEY_PWR_ON))) + { + // Switch from TV to AVR, or vice-versa. + if (deviceType == 1) + { + deviceType = 0; + } + else if (deviceType == 0) + { + deviceType = 1; + } + } + #endif // SPECIAL_XR11_POWER_DEVICETYPE_SWAP_ENABLE + + if (deviceType == 1) + { + flags |= MSO_RIB_IRRFDB_DEVICETYPE_AVR; + } + + // Construct the direct RIB entry for this RF key + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE; + ribRequest.attribute_index = (unsigned char)actionMap.rfKeyCode; + + // Decide what RF Descriptor we need to add for this RF key. + switch (actionMap.rfKeyCode) + { + case MSO_RFKEY_PWR_TOGGLE: + rfDesc = (unsigned char*)rfDescriptor_IRPowerToggle; + rfDescLength = sizeof(rfDescriptor_IRPowerToggle); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("IR Power Toggle RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_PWR_OFF: + rfDesc = (unsigned char*)rfDescriptor_DiscretePwrOff; + rfDescLength = sizeof(rfDescriptor_DiscretePwrOff); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Discrete Power Off RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_PWR_ON: + rfDesc = (unsigned char*)rfDescriptor_DiscretePwrOn; + rfDescLength = sizeof(rfDescriptor_DiscretePwrOn); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Discrete Power On RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_VOL_PLUS: + rfDesc = (unsigned char*)rfDescriptor_VolumeUp; + rfDescLength = sizeof(rfDescriptor_VolumeUp); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Volume Up RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_VOL_MINUS: + rfDesc = (unsigned char*)rfDescriptor_VolumeDown; + rfDescLength = sizeof(rfDescriptor_VolumeDown); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Volume Down RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_MUTE: + rfDesc = (unsigned char*)rfDescriptor_Mute; + rfDescLength = sizeof(rfDescriptor_Mute); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Mute RF Descriptor included, size: %d.", rfDescLength); + break; + case MSO_RFKEY_INPUT_SELECT: + rfDesc = (unsigned char*)rfDescriptor_Input; + rfDescLength = sizeof(rfDescriptor_Input); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Input RF Descriptor included, size: %d.", rfDescLength); + break; + default: + LOGWARN("No RF Descriptor included for RF Key 0x%02X.", (unsigned)actionMap.rfKeyCode); + break; + } + + // We now know what the write size will be. + total = 1 + rfDescLength + 2 + dataSize; + if (total > CTRLM_RCU_MAX_RIB_ATTRIBUTE_SIZE) + { + LOGERR("LOGIC ERROR - IR-RF DB entry length is %u, for RF Key 0x%02X, exceeds size limits!!", + total, (unsigned)actionMap.rfKeyCode); + return false; + } + ribRequest.length = (unsigned char)total; + + // Copy all the data into the ribRequest structure + *bytePtr = flags; + bytePtr++; + if (rfDesc != NULL) + { + memcpy(bytePtr, rfDesc, rfDescLength); + bytePtr += rfDescLength; + } + *bytePtr = irConfig; + bytePtr++; + *bytePtr = (unsigned char)dataSize; + bytePtr++; + memcpy(bytePtr, data, dataSize); + + LOGWARN("SET ribRequest data - total: %d, dataSize: %d, data: 0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X - " + "0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X.\n", + total, dataSize, + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], + (unsigned char)ribRequest.data[4], (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], (unsigned char)ribRequest.data[11], + (unsigned char)ribRequest.data[12], (unsigned char)ribRequest.data[13], (unsigned char)ribRequest.data[14], (unsigned char)ribRequest.data[15], + (unsigned char)ribRequest.data[16], (unsigned char)ribRequest.data[17], (unsigned char)ribRequest.data[18], (unsigned char)ribRequest.data[19]); + + // Do the direct write to the IR-RF DB RIB entry. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Set RIB IR-RF DB Request: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("%s: map set for keyName: 0x%02X, rfKeyCode: 0x%02X, %s IrCode size: %d.\n", __FUNCTION__, + actionMap.keyName, actionMap.rfKeyCode, ((deviceType == 1) ? "AVR" : "TV"), dataSize); + } + else + { + LOGERR("FAILURE result in SET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + + return true; + } // end of setKeyActionMap() + + // actionMap.keyName is a required in-parameter, all the rest are out-parameters + bool RemoteActionMappingHelper::getKeyActionMap(int deviceID, int keymapType, keyActionMap& actionMap) + { + UNUSED(keymapType); + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + unsigned char flags = 0; + unsigned char* pData = NULL; + unsigned char* pMax = NULL; + int dataSize = 0; + int rfKey = -1; + int deviceType = -1; // 0 == TV, 1 == AVR, -1 == no mapping + + if ((deviceID < 1) || (actionMap.keyName < 0x30)) + { + LOGERR("Bad arguments! Cannot get map!"); + return false; + } + + rfKey = lookupRFKey(actionMap.keyName); + if (rfKey < 0) + { + LOGERR("keyName 0x%02X does not map to a RF key! Cannot get map!", (unsigned)actionMap.keyName); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Cannot get map!"); + return false; + } + + // All we can do for now is get info from the RIB - getting info from the actual remote is not possible + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE; + ribRequest.attribute_index = (unsigned char)rfKey; + ribRequest.length = CTRLM_RCU_MAX_RIB_ATTRIBUTE_SIZE; + + // Read the RIB IRRFDB entry for the specified rfKey + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Get RIB IR-RF DB Request: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if ((ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) && (ribRequest.length > 0)) + { + flags = (unsigned char)ribRequest.data[0]; + LOGWARN("RIB data: 0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X - " + "0x%02X, 0x%02X, 0x%02X, 0x%02X - 0x%02X, 0x%02X, 0x%02X, 0x%02X.\n", + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], + (unsigned char)ribRequest.data[4], (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], (unsigned char)ribRequest.data[11], + (unsigned char)ribRequest.data[12], (unsigned char)ribRequest.data[13], (unsigned char)ribRequest.data[14], (unsigned char)ribRequest.data[15], + (unsigned char)ribRequest.data[16], (unsigned char)ribRequest.data[17], (unsigned char)ribRequest.data[18], (unsigned char)ribRequest.data[19]); + // Parse the flags byte, and find the IR code data, if present + if (flags & MSO_RIB_IRRFDB_DEFAULT_BIT) + { + // No IRCode mapping, regardless of other flag bits + deviceType = -1; + } + else if (flags & MSO_RIB_IRRFDB_IRSPECIFIED_BIT) + { + // Get the deviceType first. + deviceType = (flags & MSO_RIB_IRRFDB_DEVICETYPE_AVR) ? 1 : 0; + // There is an IR Descriptor, and we need to find it + pData = (unsigned char*)&(ribRequest.data[1]); + pMax = pData + ribRequest.length - 2; // pMax points at the last valid byte, according to ribRequest.length + if (flags & MSO_RIB_IRRFDB_RFPRESSED_BIT) + { + // Advance past the pressed descriptor + pData += pData[2] + 3; + if (pData > pMax) flags = 0; // Indicate failure by zeroing the flags + } + if (flags & MSO_RIB_IRRFDB_RFREPEATED_BIT) + { + // Advance past the repeated descriptor + pData += pData[2] + 3; + if (pData > pMax) flags = 0; + } + if (flags & MSO_RIB_IRRFDB_RFRELEASED_BIT) + { + // Advance past the released descriptor + pData += pData[2] + 3; + if (pData > pMax) flags = 0; + } + if (flags != 0) + { + // pData should be pointing to the IR descriptor now + pData++; + if (pData < pMax) + { + // Get the IRCode length + dataSize = (int)*pData; + if ((pData + dataSize) > pMax) + { + // ERROR - parsed IRCode length exceeds ribRequest.length + LOGERR("FAILURE in IR Descriptor parse - IR Code Length: %d.\n", dataSize); + return false; + } + else + { + // Leave pData pointing past the length, to the first IR data byte. + pData++; + } + } + else + { + LOGERR("FAILURE in IR Descriptor!\n"); + return false; + } + } + else + { + LOGERR("FAILURE in RF Descriptors!\n"); + return false; + } + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d, length: %d.\n", + ribRequest.result, ribRequest.length); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + LOGWARN("IR-RF DB RIB entry - deviceType: %d, dataSize: %d.\n", deviceType, dataSize); + + actionMap.rfKeyCode = rfKey; + actionMap.tvIRData.clear(); + actionMap.avrIRData.clear(); + if (deviceType == 0) + { + actionMap.tvIRData.assign(pData, pData + dataSize); + } + else if (deviceType == 1) + { + actionMap.avrIRData.assign(pData, pData + dataSize); + } + + return true; + } + + bool RemoteActionMappingHelper::clearKeyActionMap(int deviceID, int keymapType, int keyName) + { + UNUSED(keymapType); + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + int rfKey = lookupRFKey(keyName); + unsigned char flags = MSO_RIB_IRRFDB_PERMANENT_BIT | MSO_RIB_IRRFDB_DEFAULT_BIT; + + if ((deviceID < 1) || (rfKey <= 0)) + { + LOGERR("ERROR: Bad arguments - deviceID: %d, keyName: 0x%02X! Failed to clear map!", deviceID, keyName); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Failed to clear map!"); + return false; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE; + ribRequest.attribute_index = (unsigned char)rfKey; + ribRequest.length = 1 + 2 + CONTROLMGR_MAX_IR_DATA_SIZE; + ribRequest.data[0] = flags; + + // Direct write to the RIB IRRFDB entry for this RF key. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Wrote RIB IR-RF Database: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("Successfully cleared RIB IRRFDB entry for RF key 0x%02X.\n", (unsigned)rfKey); + } + else + { + LOGERR("FAILURE result in SET ribRequest! status: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + + // If we are clearing a power entry, clear the corresponding separate "device" power entry, too. + if ((rfKey == MSO_RFKEY_PWR_TOGGLE) || + (rfKey == MSO_RFKEY_PWR_OFF) || + (rfKey == MSO_RFKEY_PWR_ON)) + { + clearDevicePower(deviceID, keymapType, rfKey); + } + + return true; + } + + bool RemoteActionMappingHelper::setDevicePower(int deviceID, int keymapType, keyActionMap& actionMap) + { + byte_vector_t irData; + int tvRFKey = 0; + int avrRFKey = 0; + bool tvOK = true; + bool avrOK = true; + + if (deviceID < 1) + { + LOGERR("ERROR: Bad argument - deviceID: %d!", deviceID); + return false; + } + + if ((actionMap.rfKeyCode != MSO_RFKEY_PWR_TOGGLE) && + (actionMap.rfKeyCode != MSO_RFKEY_PWR_OFF) && + (actionMap.rfKeyCode != MSO_RFKEY_PWR_ON)) + { + LOGERR("ERROR: Bad rfKeyCode in actionMap: 0x%02X!", actionMap.rfKeyCode); + return false; + } + + // Translate from the original 3 rfKeyCodes to the appropriate separate TV and AVR RF key codes + switch (actionMap.rfKeyCode) + { + case MSO_RFKEY_PWR_TOGGLE: + tvRFKey = XRC_RFKEY_TV_PWR_TOGGLE; + avrRFKey = XRC_RFKEY_AVR_PWR_TOGGLE; + LOGWARN("Power Toggle - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + case MSO_RFKEY_PWR_OFF: + tvRFKey = XRC_RFKEY_TV_PWR_OFF; + avrRFKey = XRC_RFKEY_AVR_PWR_OFF; + LOGWARN("Discrete Power Off - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + case MSO_RFKEY_PWR_ON: + tvRFKey = XRC_RFKEY_TV_PWR_ON; + avrRFKey = XRC_RFKEY_AVR_PWR_ON; + LOGWARN("Discrete Power On - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + } + + // We ignore the "srcInfo" decisions, and either set or clear, according to what is available in the actionMap, + irData.clear(); + if (actionMap.tvIRData.size() > 0) + { + // We have TV IR data, so we should set the separate TV slot for the key + irData = actionMap.tvIRData; + LOGWARN("Setting separate TV key slot 0x%02X.", tvRFKey); + tvOK = setRIBDevicePower(deviceID, keymapType, tvRFKey, irData); + } + else + { + // We should clear the separate TV slot for the key + LOGWARN("Clearing separate TV key slot 0x%02X.", tvRFKey); + tvOK = clearRIBDevicePower(deviceID, keymapType, tvRFKey); + } + irData.clear(); + if (actionMap.avrIRData.size() > 0) + { + // We have AVR IR data, so we should set the separate AVR slot for the key + irData = actionMap.avrIRData; + LOGWARN("Setting separate AVR key slot 0x%02X.", avrRFKey); + avrOK = setRIBDevicePower(deviceID, keymapType, avrRFKey, irData); + } + else + { + // We should clear the separate AVR slot for the key + LOGWARN("Clearing separate AVR key slot 0x%02X.", avrRFKey); + avrOK = clearRIBDevicePower(deviceID, keymapType, avrRFKey); + } + + return (tvOK && avrOK); + } + + bool RemoteActionMappingHelper::clearDevicePower(int deviceID, int keymapType, int rfKeyCode) + { + int tvRFKey = 0; + int avrRFKey = 0; + bool tvOK = true; + bool avrOK = true; + + if (deviceID < 1) + { + LOGERR("ERROR: Bad deviceID: %d!", deviceID); + return false; + } + + if ((rfKeyCode != MSO_RFKEY_PWR_TOGGLE) && + (rfKeyCode != MSO_RFKEY_PWR_OFF) && + (rfKeyCode != MSO_RFKEY_PWR_ON)) + { + LOGERR("ERROR: Bad rfKeyCode: 0x%02X!", rfKeyCode); + return false; + } + + // Translate from the original 3 rfKeyCodes to the appropriate separate TV and AVR RF key codes + switch (rfKeyCode) + { + case MSO_RFKEY_PWR_TOGGLE: + tvRFKey = XRC_RFKEY_TV_PWR_TOGGLE; + avrRFKey = XRC_RFKEY_AVR_PWR_TOGGLE; + LOGWARN("Power Toggle - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + case MSO_RFKEY_PWR_OFF: + tvRFKey = XRC_RFKEY_TV_PWR_OFF; + avrRFKey = XRC_RFKEY_AVR_PWR_OFF; + LOGWARN("Discrete Power Off - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + case MSO_RFKEY_PWR_ON: + tvRFKey = XRC_RFKEY_TV_PWR_ON; + avrRFKey = XRC_RFKEY_AVR_PWR_ON; + LOGWARN("Discrete Power On - TV key slot 0x%02X, AVR key slot 0x%02X.", tvRFKey, avrRFKey); + break; + } + LOGWARN("Clearing separate TV key slot 0x%02X.", tvRFKey); + tvOK = clearRIBDevicePower(deviceID, keymapType, tvRFKey); + LOGWARN("Clearing separate AVR key slot 0x%02X.", avrRFKey); + avrOK = clearRIBDevicePower(deviceID, keymapType, avrRFKey); + + return (tvOK && avrOK); + } + + bool RemoteActionMappingHelper::setRIBDevicePower(int deviceID, int keymapType, int rfKeyCode, byte_vector_t& irData) + { + UNUSED(keymapType); + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + int dataSize = irData.size(); + unsigned char* data = irData.data(); + unsigned char flags = MSO_RIB_IRRFDB_PERMANENT_BIT | MSO_RIB_IRRFDB_IRSPECIFIED_BIT; + unsigned char irConfig = 0; + unsigned char* bytePtr = (unsigned char*)&(ribRequest.data[0]); + unsigned char* rfDesc = NULL; + size_t rfDescLength = 0; + size_t total = 0; + + if ((deviceID < 1) || (irData.size() <= 1)) + { + LOGERR("ERROR - Bad arguments - deviceID: %d, data size: %d!! Cannot set device power IRRFDB entry!", + deviceID, irData.size()); + return false; + } + + if (!((rfKeyCode == XRC_RFKEY_TV_PWR_TOGGLE) || + (rfKeyCode == XRC_RFKEY_TV_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_TV_PWR_ON) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_TOGGLE) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_ON) )) + { + LOGERR("ERROR - Wrong rfKeyCode(0x%02X) - NOT a device power rfKeyCode code!!", rfKeyCode); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Cannot set map!"); + return false; + } + + if (dataSize > CONTROLMGR_MAX_IR_DATA_SIZE) + { + LOGERR("LOGIC ERROR - IRCode dataSize %d, for RF Key 0x%02X, exceeds size limits!", + dataSize, (unsigned)rfKeyCode); + return false; + } + + // Construct the direct RIB entry for this RF key + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE; + ribRequest.attribute_index = (unsigned char)rfKeyCode; + + // Decide what RF Descriptor we need to add for this RF key. + switch (rfKeyCode) + { + case XRC_RFKEY_TV_PWR_TOGGLE: + case XRC_RFKEY_AVR_PWR_TOGGLE: + rfDesc = (unsigned char*)rfDescriptor_IRPowerToggle; + rfDescLength = sizeof(rfDescriptor_IRPowerToggle); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("IR Power Toggle RF Descriptor included, size: %d.", rfDescLength); + break; + case XRC_RFKEY_TV_PWR_OFF: + case XRC_RFKEY_AVR_PWR_OFF: + rfDesc = (unsigned char*)rfDescriptor_DiscretePwrOff; + rfDescLength = sizeof(rfDescriptor_DiscretePwrOff); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Discrete Power Off RF Descriptor included, size: %d.", rfDescLength); + break; + case XRC_RFKEY_TV_PWR_ON: + case XRC_RFKEY_AVR_PWR_ON: + rfDesc = (unsigned char*)rfDescriptor_DiscretePwrOn; + rfDescLength = sizeof(rfDescriptor_DiscretePwrOn); + flags |= MSO_RIB_IRRFDB_RFRELEASED_BIT; + LOGWARN("Discrete Power On RF Descriptor included, size: %d.", rfDescLength); + break; + default: + LOGERR("LOGIC ERROR - Invalid separate power RF Key 0x%02X.", (unsigned)rfKeyCode); + break; + } + + // Set the DeviceType flags field properly + if ((rfKeyCode == XRC_RFKEY_AVR_PWR_TOGGLE) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_ON)) + { + flags |= MSO_RIB_IRRFDB_DEVICETYPE_AVR; + } + + // Set the IR Config byte properly + if ((rfKeyCode == XRC_RFKEY_TV_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_TV_PWR_ON) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_ON)) + { + irConfig = 0x4F; // Tweak database, min transmissions = 0xF, only for discrete power + } + + // We now know what the write size will be. + total = 1 + rfDescLength + 2 + dataSize; + if (total > CTRLM_RCU_MAX_RIB_ATTRIBUTE_SIZE) + { + LOGERR("LOGIC ERROR - IR-RF DB entry length is %u, for RF Key 0x%02X, exceeds size limits!!", + total, (unsigned)rfKeyCode); + return false; + } + ribRequest.length = (unsigned char)total; + + // Copy all the data into the ribRequest structure + *bytePtr = flags; + bytePtr++; + if (rfDesc != NULL) + { + memcpy(bytePtr, rfDesc, rfDescLength); + bytePtr += rfDescLength; + } + *bytePtr = irConfig; + bytePtr++; + *bytePtr = (unsigned char)dataSize; + bytePtr++; + memcpy(bytePtr, data, dataSize); + + // Do the direct write to the IR-RF DB RIB entry. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Set RIB IR-RF DB Request: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("separate map set for rfKeyCode: 0x%02X, IrCode size: %d.\n", + (unsigned)rfKeyCode, dataSize); + } + else + { + LOGERR("FAILURE result in SET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + + return true; + } // end of setRIBDevicePower() + + bool RemoteActionMappingHelper::clearRIBDevicePower(int deviceID, int keymapType, int rfKeyCode) + { + UNUSED(keymapType); + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + unsigned char flags = MSO_RIB_IRRFDB_PERMANENT_BIT | MSO_RIB_IRRFDB_DEFAULT_BIT; + + if ((deviceID < 1) || (rfKeyCode <= 0)) + { + LOGERR("ERROR: Bad arguments - deviceID: %d, rfKeyCode: 0x%02X! Failed to clear map!", deviceID, rfKeyCode); + return false; + } + + if (!((rfKeyCode == XRC_RFKEY_TV_PWR_TOGGLE) || + (rfKeyCode == XRC_RFKEY_TV_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_TV_PWR_ON) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_TOGGLE) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_OFF) || + (rfKeyCode == XRC_RFKEY_AVR_PWR_ON) )) + { + LOGERR("ERROR - Wrong rfKeyCode(0x%02X) - NOT a device power RFKey code!!", rfKeyCode); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Failed to clear map!"); + return false; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE; + ribRequest.attribute_index = (unsigned char)rfKeyCode; + ribRequest.length = 1 + 2 + CONTROLMGR_MAX_IR_DATA_SIZE; + ribRequest.data[0] = flags; + + // Direct write to the RIB IRRFDB entry for this RF key. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Wrote RIB IR-RF Database: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("Successfully cleared separate power slot for rfKeyCode 0x%02X.\n", (unsigned)rfKeyCode); + } + else + { + LOGERR("FAILURE result in SET ribRequest! status: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + + return true; + } // end of clearRIBDevicePower + + // Note that, regardless of how we set the IRRF Database-related IRRF Status Flags, we will clear all 5-Digit Code-related flags here. + bool RemoteActionMappingHelper::setIRDBDownloadFlag(int deviceID, bool bDownload) + { + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + unsigned char flags = (unsigned char)(bDownload ? XRC_RIB_IRRFSTATUS_DOWNLOAD_IRDB_BIT : XRC_RIB_IRRFSTATUS_DONT_DOWNLOAD_IRDB_BIT); + + if (deviceID < 1) + { + LOGERR("Bad deviceID: %d! Unable to change flags!", deviceID); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Unable to change flags!"); + return false; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS; + ribRequest.attribute_index = 0; // TODO: XRC says this is supposed to be the "key" - what does that mean in this context? + ribRequest.length = 1; + + // Read the RIB IRRF Status to get the current Flags setting + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Current RIB IR-RF Status: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + if (((unsigned char)ribRequest.data[0]) != flags) + { + // Write the IRRF Status Flags byte in the RIB + ribRequest.data[0] = flags; + + // If the length was changed by controlMgr, set it back to 1 + if(ribRequest.length != 1) + { + LOGWARN("Length changed by controlMgr in GET ribRequest from 1 to %d! Setting back to 1.\n", ribRequest.length); + ribRequest.length = 1; + } + + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("Set RIB IR-RF Status success: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + } + else + { + LOGERR("FAILURE result in SET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + } + else + { + LOGWARN("Not writing IRRF Status Flags - flags already set to 0x%02X.\n", (unsigned char)ribRequest.data[0]); + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + return true; + } + + bool RemoteActionMappingHelper::setFiveDigitCode(int deviceID, int tvFiveDigitCode, int avrFiveDigitCode) + { + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + char codestr[6]; + unsigned char flags = 0; + + if (deviceID < 1) + { + LOGERR("Bad deviceID: %d! Unable to change flags!", deviceID); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Unable to change flags!"); + return false; + } + + if ((tvFiveDigitCode == 0) && (avrFiveDigitCode == 0)) + { + // Nothing to set into the Target IRDB Status - what is wanted is a Clear All operation - not done here. + return true; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_TARGET_IRDB_STATUS; + ribRequest.attribute_index = 0; // TODO: XRC says this is supposed to be the "key" - what does that mean in this context? + ribRequest.length = CTRLM_RCU_RIB_ATTR_LEN_TARGET_IRDB_STATUS; + + // Read the entire RIB Target IRDB Status attribute, to get the current Flags and TV and AVR strings. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Current RIB Target IRDB Status: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + if (ribRequest.length == 13) + { + LOGWARN("Target IRDB Status Data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, " + "0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X.", + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], + (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], (unsigned char)ribRequest.data[4], + (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], + (unsigned char)ribRequest.data[11], (unsigned char)ribRequest.data[12]); + } + flags = (unsigned char)ribRequest.data[0]; + // Write the TV and/or AVR 5-digit codes, as strings, to the data area. + if (tvFiveDigitCode > 0) + { + memset((void*)codestr, 0, sizeof(codestr)); + snprintf(codestr, 6, "%05u", tvFiveDigitCode); + memcpy((void*)&ribRequest.data[1], (const void*)codestr, 6); + flags |= XRC_RIB_TARGET_STATUS_FLAGS_TV_CODE_PRESENT_BIT; + // Clear the Not Programmed and/or IRRF Database bits, if they were set. + if ((flags & (XRC_RIB_TARGET_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT | XRC_RIB_TARGET_STATUS_FLAGS_IRRF_DATABASE_BIT)) != 0) + { + flags &= ~(XRC_RIB_TARGET_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT | XRC_RIB_TARGET_STATUS_FLAGS_IRRF_DATABASE_BIT); + } + } + if (avrFiveDigitCode > 0) + { + memset((void*)codestr, 0, sizeof(codestr)); + snprintf(codestr, 6, "%05u", avrFiveDigitCode); + memcpy((void*)&ribRequest.data[7], (const void*)codestr, 6); + flags |= XRC_RIB_TARGET_STATUS_FLAGS_AVR_CODE_PRESENT_BIT; + // Clear the Not Programmed and/or IRRF Database bits, if they were set. + if ((flags & (XRC_RIB_TARGET_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT | XRC_RIB_TARGET_STATUS_FLAGS_IRRF_DATABASE_BIT)) != 0) + { + flags &= ~(XRC_RIB_TARGET_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT | XRC_RIB_TARGET_STATUS_FLAGS_IRRF_DATABASE_BIT); + } + } + // Update the flags byte in the request. + ribRequest.data[0] = flags; + // Write the Target IRDB Status attribute back to the RIB. + ribRequest.length = CTRLM_RCU_RIB_ATTR_LEN_TARGET_IRDB_STATUS; + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("Set RIB Target IRDB Status success: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length); + if (ribRequest.length == 13) + { + LOGWARN("new Target IRDB Status Data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, " + "0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X.", + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], + (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], (unsigned char)ribRequest.data[4], + (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], + (unsigned char)ribRequest.data[11], (unsigned char)ribRequest.data[12]); + } + } + else + { + LOGERR("FAILURE result in SET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + return true; + } // end of setFiveDigitCode() + + // Note that, regardless of how we set the 5-Digit Code-related IRRF Status Flags, we will clear all IRRF Database-related flags here. + bool RemoteActionMappingHelper::setFiveDigitCodeFlags(int deviceID, int mode) + { + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + unsigned char flags = (unsigned char)XRC_RIB_IRRFSTATUS_DONT_DOWNLOAD_IRDB_BIT; + + if (deviceID < 1) + { + LOGERR("Bad deviceID: %d! Unable to change flags!", deviceID); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Unable to change flags!"); + return false; + } + + // Translate the "mode" into the flags we want to set. + switch (mode) + { + case FIVE_DIGIT_CODE_MODE_NONE: flags |= 0; break; // Set NO additional flags + case FIVE_DIGIT_CODE_MODE_TV_SET: flags |= XRC_RIB_IRRFSTATUS_DOWNLOAD_TV_5DCODE_BIT; break; + case FIVE_DIGIT_CODE_MODE_AVR_SET: flags |= XRC_RIB_IRRFSTATUS_DOWNLOAD_AVR_5DCODE_BIT; break; + case FIVE_DIGIT_CODE_MODE_TVAVR_SET: flags |= (XRC_RIB_IRRFSTATUS_DOWNLOAD_TV_5DCODE_BIT | XRC_RIB_IRRFSTATUS_DOWNLOAD_AVR_5DCODE_BIT); break; + case FIVE_DIGIT_CODE_MODE_CLEAR: flags |= XRC_RIB_IRRFSTATUS_CLEAR_ALL_5DCODES_BIT; break; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS; + ribRequest.attribute_index = 0; // TODO: XRC says this is supposed to be the "key" - what does that mean in this context? + ribRequest.length = 1; + + // Read the RIB IRRF Status to get the current Flags setting + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Current RIB IR-RF Status: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + if (((unsigned char)ribRequest.data[0]) != flags) + { + // Write the IRRF Status Flags byte in the RIB + ribRequest.data[0] = flags; + + // If the length was changed by controlMgr, set it back to 1 + if(ribRequest.length != 1) + { + LOGWARN("Length changed by controlMgr in GET ribRequest from 1 to %d! Setting back to 1.\n", ribRequest.length); + ribRequest.length = 1; + } + + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_SET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + LOGWARN("Set RIB IR-RF Status success: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + } + else + { + LOGERR("FAILURE result in SET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_SET! return value: %d.\n", res); + return false; + } + } + else + { + LOGERR("Not writing IRRF Status Flags - flags already set to 0x%02X.\n", (unsigned char)ribRequest.data[0]); + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + return true; + } // end of setFiveDigitCodeFlags() + + bool RemoteActionMappingHelper::getControllerLoadStatus(int deviceID, unsigned& tvLoadStatus, unsigned& avrLoadStatus) + { + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + + if (deviceID < 1) + { + LOGERR("Bad deviceID: %d! Unable to change flags!", deviceID); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Unable to change flags!"); + return false; + } + + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_CONTROLLER_IRDB_STATUS; + ribRequest.attribute_index = 0; // TODO: XRC says this is supposed to be the "key" - what does that mean in this context? + ribRequest.length = CTRLM_RCU_RIB_ATTR_LEN_CONTROLLER_IRDB_STATUS; + + // Read the entire RIB Controller IRDB Status attribute, to get the current TV and AVR Load Status bytes. + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Current RIB Controller IRDB Status: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + if (ribRequest.length == 13) + { + LOGWARN("Controller IRDB Status Data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, " + "0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X.", + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], + (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], (unsigned char)ribRequest.data[4], + (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], + (unsigned char)ribRequest.data[11], (unsigned char)ribRequest.data[12]); + } + else if (ribRequest.length == 15) + { + LOGWARN("Controller IRDB Status Data: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, " + "0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X.", + (unsigned char)ribRequest.data[0], (unsigned char)ribRequest.data[1], + (unsigned char)ribRequest.data[2], (unsigned char)ribRequest.data[3], (unsigned char)ribRequest.data[4], + (unsigned char)ribRequest.data[5], (unsigned char)ribRequest.data[6], (unsigned char)ribRequest.data[7], + (unsigned char)ribRequest.data[8], (unsigned char)ribRequest.data[9], (unsigned char)ribRequest.data[10], + (unsigned char)ribRequest.data[11], (unsigned char)ribRequest.data[12], + (unsigned char)ribRequest.data[13], (unsigned char)ribRequest.data[14]); + } + tvLoadStatus = 0; + if (ribRequest.length == 15) + { + tvLoadStatus = ribRequest.data[13]; + } + avrLoadStatus = 0; + if (ribRequest.length == 15) + { + avrLoadStatus = ribRequest.data[14]; + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + return true; + } // end of getControllerLoadStatus() + + bool RemoteActionMappingHelper::cancelCodeDownload(int deviceID) + { + ctrlm_rcu_iarm_call_rib_request_t ribRequest; + ctrlm_network_id_t rf4ceId; + IARM_Result_t res; + unsigned char flags = 0; + bool bIRRFDBPending = false; + + if (deviceID < 1) + { + LOGERR("Bad deviceID: %d! Unable to change flags!", deviceID); + return false; + } + + rf4ceId = getRf4ceNetworkID(); + if (rf4ceId == CTRLM_MAIN_NETWORK_ID_INVALID) + { + LOGERR("FAILURE - No RF4CE network_id found! Unable to change flags!"); + return false; + } + + // Find out if there is a IRRF Database request waiting to go off. + memset((void*)&ribRequest, 0, sizeof(ctrlm_rcu_iarm_call_rib_request_t)); + ribRequest.api_revision = CTRLM_RCU_IARM_BUS_API_REVISION; + ribRequest.network_id = rf4ceId; + ribRequest.controller_id = deviceID; + ribRequest.attribute_id = CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS; + ribRequest.attribute_index = 0; // TODO: XRC says this is supposed to be the "key" - what does that mean in this context? + ribRequest.length = 1; + + // Read the RIB IRRF Status to get the current Flags setting + res = IARM_Bus_Call(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_CALL_RIB_REQUEST_GET, (void *)&ribRequest, sizeof(ribRequest)); + if (res == IARM_RESULT_SUCCESS) + { + LOGWARN("Current RIB IR-RF Status: controller_id: %u, network_id: 0x%02X, " + "attribute_id: 0x%02X, attribute_index: 0x%02X, result: %u, length: %u, data[0]: 0x%02X.", + ribRequest.controller_id, ribRequest.network_id, (unsigned char)ribRequest.attribute_id, + ribRequest.attribute_index, ribRequest.result, ribRequest.length, (unsigned char)ribRequest.data[0]); + if (ribRequest.result == CTRLM_IARM_CALL_RESULT_SUCCESS) + { + flags = ((unsigned char)ribRequest.data[0]); + if ((flags & (XRC_RIB_IRRFSTATUS_DOWNLOAD_IRDB_BIT | XRC_RIB_IRRFSTATUS_DONT_DOWNLOAD_IRDB_BIT)) == XRC_RIB_IRRFSTATUS_DOWNLOAD_IRDB_BIT) + { + // There was a IRRF Database download pending - clear the IRRF Databass entries. + bIRRFDBPending = true; + } + else + { + LOGWARN("Not clearing IRRF Database entries - no download pending.\n"); + } + } + else + { + LOGERR("FAILURE result in GET ribRequest! result: %d.\n", ribRequest.result); + return false; + } + } + else + { + LOGERR("FAILURE in bus call RIB_REQUEST_GET! return value: %d.\n", res); + return false; + } + + // This clears all 5-digit code flags, and sets the IRRF Database flags back to normal idle (Do Not Download). + setFiveDigitCodeFlags(deviceID, FIVE_DIGIT_CODE_MODE_NONE); + if (bIRRFDBPending) + { + // Clear all the IRRF Database entries. + clearKeyActionMap(deviceID, 0, KED_VOLUMEUP); + clearKeyActionMap(deviceID, 0, KED_VOLUMEDOWN); + clearKeyActionMap(deviceID, 0, KED_MUTE); + clearKeyActionMap(deviceID, 0, KED_INPUTKEY); + clearKeyActionMap(deviceID, 0, KED_POWER); + clearKeyActionMap(deviceID, 0, KED_DISCRETE_POWER_ON); + clearKeyActionMap(deviceID, 0, KED_DISCRETE_POWER_STANDBY); + LOGWARN("Pending IRRF Database download cancelled - all entries cleared!\n"); + } + + return true; + } + + int RemoteActionMappingHelper::lookupRFKey(int keyName) + { + int rfKey = -1; + + switch(keyName) + { + case KED_VOLUMEUP: + rfKey = MSO_RFKEY_VOL_PLUS; + break; + case KED_VOLUMEDOWN: + rfKey = MSO_RFKEY_VOL_MINUS; + break; + case KED_MUTE: + rfKey = MSO_RFKEY_MUTE; + break; + case KED_INPUTKEY: + rfKey = MSO_RFKEY_INPUT_SELECT; + break; + case KED_TVPOWER: + rfKey = MSO_RFKEY_PWR_TOGGLE; + break; + case KED_POWER: + rfKey = MSO_RFKEY_PWR_TOGGLE; + break; + case KED_RF_POWER: + rfKey = MSO_RFKEY_PWR_TOGGLE; + break; + case KED_DISCRETE_POWER_ON: + rfKey = MSO_RFKEY_PWR_ON; + break; + case KED_DISCRETE_POWER_STANDBY: + rfKey = MSO_RFKEY_PWR_OFF; + break; + } + + return rfKey; + } + + + int RemoteActionMappingHelper::lookupKeyname(int rfKey) + { + int keyName = -1; + + switch(rfKey) + { + case MSO_RFKEY_VOL_PLUS: + keyName = KED_VOLUMEUP; + break; + case MSO_RFKEY_VOL_MINUS: + keyName = KED_VOLUMEDOWN; + break; + case MSO_RFKEY_MUTE: + keyName = KED_MUTE; + break; + case MSO_RFKEY_INPUT_SELECT: + keyName = KED_INPUTKEY; + break; + case MSO_RFKEY_PWR_TOGGLE: + keyName = KED_POWER; + break; + case MSO_RFKEY_PWR_ON: + keyName = KED_DISCRETE_POWER_ON; + break; + case MSO_RFKEY_PWR_OFF: + keyName = KED_DISCRETE_POWER_STANDBY; + break; + // Map the 6 separate power RF code back to the appropriate/related keynames, too + case XRC_RFKEY_TV_PWR_TOGGLE: + keyName = KED_POWER; + break; + case XRC_RFKEY_TV_PWR_ON: + keyName = KED_DISCRETE_POWER_ON; + break; + case XRC_RFKEY_TV_PWR_OFF: + keyName = KED_DISCRETE_POWER_STANDBY; + break; + case XRC_RFKEY_AVR_PWR_TOGGLE: + keyName = KED_POWER; + break; + case XRC_RFKEY_AVR_PWR_ON: + keyName = KED_DISCRETE_POWER_ON; + break; + case XRC_RFKEY_AVR_PWR_OFF: + keyName = KED_DISCRETE_POWER_STANDBY; + break; + } + + return keyName; + } + + } // namespace Plugin + +} // namespace WPEFramework diff --git a/RemoteActionMapping/RamHelper.h b/RemoteActionMapping/RamHelper.h new file mode 100644 index 0000000000..133f698834 --- /dev/null +++ b/RemoteActionMapping/RamHelper.h @@ -0,0 +1,308 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +//IARM includes +#include "libIBus.h" + +#include "ctrlm_ipc.h" +#include "ctrlm_ipc_rcu.h" + +#include +#include + +#include "comcastIrKeyCodes.h" + +typedef std::vector byte_vector_t; +typedef std::vector int_vector_t; + +struct controller_info { + ctrlm_network_id_t network_id; + ctrlm_controller_id_t controller_id; + ctrlm_controller_status_t status; +}; + +struct rf4ceBindRemotes_t { + int numBindRemotes; + controller_info remotes[CTRLM_MAIN_MAX_BOUND_CONTROLLERS]; +}; + +// Mapped MSO RF Keys +#define MSO_RFKEY_INPUT_SELECT 0x034 + +#define MSO_RFKEY_VOL_PLUS 0x041 +#define MSO_RFKEY_VOL_MINUS 0x042 +#define MSO_RFKEY_MUTE 0x043 + +#define XRC_RFKEY_TV_PWR_TOGGLE 0x040 // 3 new TV power keycodes (XRC Profile update) +#define XRC_RFKEY_TV_PWR_ON 0x04D +#define XRC_RFKEY_TV_PWR_OFF 0x04E + +#define XRC_RFKEY_AVR_PWR_TOGGLE 0x068 // 3 new AVR power keycodes (XRC Profile update) +#define XRC_RFKEY_AVR_PWR_OFF 0x069 +#define XRC_RFKEY_AVR_PWR_ON 0x06A + +#define MSO_RFKEY_PWR_TOGGLE 0x06B // Older power keycodes +#define MSO_RFKEY_PWR_OFF 0x06C +#define MSO_RFKEY_PWR_ON 0x06D + +// IR-RF Database Flags byte bit masks +#define MSO_RIB_IRRFDB_PERMANENT_BIT 0x080 +#define MSO_RIB_IRRFDB_DEFAULT_BIT 0x040 +#define MSO_RIB_IRRFDB_DEVICETYPE_BITS 0x030 +#define MSO_RIB_IRRFDB_DEVICETYPE_AVR 0x020 +#define MSO_RIB_IRRFDB_IRSPECIFIED_BIT 0x008 +#define MSO_RIB_IRRFDB_RFRELEASED_BIT 0x004 +#define MSO_RIB_IRRFDB_RFREPEATED_BIT 0x002 +#define MSO_RIB_IRRFDB_RFPRESSED_BIT 0x001 + +// IR-RF Status Flags byte bit masks +#define XRC_RIB_IRRFSTATUS_DOWNLOAD_IRDB_BIT 0x080 +#define XRC_RIB_IRRFSTATUS_DONT_DOWNLOAD_IRDB_BIT 0x040 +#define XRC_RIB_IRRFSTATUS_CLEAR_ALL_5DCODES_BIT 0x010 +#define XRC_RIB_IRRFSTATUS_TRANSMIT_DESCRIPTOR_BIT 0x008 +#define XRC_RIB_IRRFSTATUS_DOWNLOAD_AVR_5DCODE_BIT 0x004 +#define XRC_RIB_IRRFSTATUS_DOWNLOAD_TV_5DCODE_BIT 0x002 +#define XRC_RIB_IRRFSTATUS_FORCE_IRDB_BIT 0x001 + +// Controller IRDB Status Flags byte bit masks +#define XRC_RIB_CTRLR_STATUS_FLAGS_5DIGIT_SUPPORT_BIT 0x080 +#define XRC_RIB_CTRLR_STATUS_FLAGS_AVR_CODE_PRESENT_BIT 0x010 +#define XRC_RIB_CTRLR_STATUS_FLAGS_TV_CODE_PRESENT_BIT 0x008 +#define XRC_RIB_CTRLR_STATUS_FLAGS_IRRF_DATABASE_BIT 0x004 +#define XRC_RIB_CTRLR_STATUS_FLAGS_IRDB_TYPE_BIT 0x002 +#define XRC_RIB_CTRLR_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT 0x001 + +// Controller IRDB Status Load Status byte bit masks +#define XRC_RIB_CTRLR_STATUS_LOAD_RESULT_BITS 0x0F0 + +// Target IRDB Status Flags byte bit masks +#define XRC_RIB_TARGET_STATUS_FLAGS_AVR_CODE_PRESENT_BIT 0x010 +#define XRC_RIB_TARGET_STATUS_FLAGS_TV_CODE_PRESENT_BIT 0x008 +#define XRC_RIB_TARGET_STATUS_FLAGS_IRRF_DATABASE_BIT 0x004 +#define XRC_RIB_TARGET_STATUS_FLAGS_NO_IR_PROGRAMMED_BIT 0x001 + +// Maximum size (in bytes) of the IR waveform data +#define CONTROLMGR_MAX_IR_DATA_SIZE (80) + + +// Enumeration for combinations of 5-Digit Code flags in the IR-RF Status Flags byte. +typedef enum { + FIVE_DIGIT_CODE_MODE_NONE, + FIVE_DIGIT_CODE_MODE_TV_SET, + FIVE_DIGIT_CODE_MODE_AVR_SET, + FIVE_DIGIT_CODE_MODE_TVAVR_SET, + FIVE_DIGIT_CODE_MODE_CLEAR +} FiveDigitCodeMode; + +// Definitions used for key group decision making +typedef enum { + KEY_GROUP_SRC_CLEAR, + KEY_GROUP_SRC_TV, + KEY_GROUP_SRC_AVR, + KEY_GROUP_SRC_TV_PWR_CROSS, // xxx_PWR_CROSS are only used for the DiscretePower group, + KEY_GROUP_SRC_AVR_PWR_CROSS // to implement DiscretePower when only TogglePower is available. +} KeyGroupSrc; + +class KeyGroupSrcInfo { +public: + KeyGroupSrc groupInputSelect; + KeyGroupSrc groupVolume; // Includes VOL_UP and VOL_DOWN + KeyGroupSrc groupMute; + KeyGroupSrc groupTogglePower; + KeyGroupSrc groupDiscretePower; // Includes Discrete Power ON and OFF + + KeyGroupSrcInfo() { groupInputSelect = groupVolume = groupMute = groupTogglePower = groupDiscretePower = KEY_GROUP_SRC_CLEAR; } +}; + +// Classes to aid in key group decisions (for the original 7 IRRFDB slots, especially the legacy power slots) +class RFKeyFlags { +public: + bool input; + bool vol_up; + bool vol_dn; + bool mute; + bool pwr_toggle; + bool pwr_on; + bool pwr_off; + + RFKeyFlags() { input = vol_up = vol_dn = mute = pwr_toggle = pwr_on = pwr_off = false; } + + void setKey(int rfKey) { + switch (rfKey) { + case MSO_RFKEY_INPUT_SELECT: input = true; break; + case MSO_RFKEY_VOL_PLUS: vol_up = true; break; + case MSO_RFKEY_VOL_MINUS: vol_dn = true; break; + case MSO_RFKEY_MUTE: mute = true; break; + case MSO_RFKEY_PWR_TOGGLE: pwr_toggle = true; break; + case MSO_RFKEY_PWR_OFF: pwr_off = true; break; + case MSO_RFKEY_PWR_ON: pwr_on = true; break; + } + } + + void clearKey(int rfKey) { + switch (rfKey) { + case MSO_RFKEY_INPUT_SELECT: input = false; break; + case MSO_RFKEY_VOL_PLUS: vol_up = false; break; + case MSO_RFKEY_VOL_MINUS: vol_dn = false; break; + case MSO_RFKEY_MUTE: mute = false; break; + case MSO_RFKEY_PWR_TOGGLE: pwr_toggle = false; break; + case MSO_RFKEY_PWR_OFF: pwr_off = false; break; + case MSO_RFKEY_PWR_ON: pwr_on = false; break; + } + } + + bool isSet(int rfKey) { + bool isset = false; + switch (rfKey) { + case MSO_RFKEY_INPUT_SELECT: isset = input; break; + case MSO_RFKEY_VOL_PLUS: isset = vol_up; break; + case MSO_RFKEY_VOL_MINUS: isset = vol_dn; break; + case MSO_RFKEY_MUTE: isset = mute; break; + case MSO_RFKEY_PWR_TOGGLE: isset = pwr_toggle; break; + case MSO_RFKEY_PWR_OFF: isset = pwr_off; break; + case MSO_RFKEY_PWR_ON: isset = pwr_on; break; + } + return isset; + } + +}; + +class KeyPresenceFlags { +public: + RFKeyFlags tv; + RFKeyFlags avr; +}; + +// Class used for evaluating the target controller's IR-RF Database read/load progress +class IRRFDBCtrlrLoadProgress { +public: + bool slot_INPUT_SELECT; + bool slot_VOL_PLUS; + bool slot_VOL_MINUS; + bool slot_MUTE; + bool slot_TV_PWR_TOGGLE; + bool slot_TV_PWR_ON; + bool slot_TV_PWR_OFF; + bool slot_AVR_PWR_TOGGLE; + bool slot_AVR_PWR_OFF; + bool slot_AVR_PWR_ON; + bool slot_PWR_TOGGLE; + bool slot_PWR_OFF; + bool slot_PWR_ON; + + IRRFDBCtrlrLoadProgress() { + clear(); + } + + void clear() { + slot_INPUT_SELECT = slot_VOL_PLUS = slot_VOL_MINUS = slot_MUTE = + slot_TV_PWR_TOGGLE = slot_TV_PWR_ON = slot_TV_PWR_OFF = + slot_AVR_PWR_TOGGLE = slot_AVR_PWR_OFF = slot_AVR_PWR_ON = + slot_PWR_TOGGLE = slot_PWR_OFF = slot_PWR_ON = false; + } + + void setSlotRead(int rfKey, bool state) { + switch (rfKey) { + case MSO_RFKEY_INPUT_SELECT: slot_INPUT_SELECT = state; break; + case MSO_RFKEY_VOL_PLUS: slot_VOL_PLUS = state; break; + case MSO_RFKEY_VOL_MINUS: slot_VOL_MINUS = state; break; + case MSO_RFKEY_MUTE: slot_MUTE = state; break; + case XRC_RFKEY_TV_PWR_TOGGLE: slot_TV_PWR_TOGGLE = state; break; + case XRC_RFKEY_TV_PWR_ON: slot_TV_PWR_ON = state; break; + case XRC_RFKEY_TV_PWR_OFF: slot_TV_PWR_OFF = state; break; + case XRC_RFKEY_AVR_PWR_TOGGLE: slot_AVR_PWR_TOGGLE = state; break; + case XRC_RFKEY_AVR_PWR_OFF: slot_AVR_PWR_OFF = state; break; + case XRC_RFKEY_AVR_PWR_ON: slot_AVR_PWR_ON = state; break; + case MSO_RFKEY_PWR_TOGGLE: slot_PWR_TOGGLE = state; break; + case MSO_RFKEY_PWR_OFF: slot_PWR_OFF = state; break; + case MSO_RFKEY_PWR_ON: slot_PWR_ON = state; break; + } + } + + bool getSlotRead(int rfKey) { + bool state = false; + switch (rfKey) { + case MSO_RFKEY_INPUT_SELECT: state = slot_INPUT_SELECT; break; + case MSO_RFKEY_VOL_PLUS: state = slot_VOL_PLUS; break; + case MSO_RFKEY_VOL_MINUS: state = slot_VOL_MINUS; break; + case MSO_RFKEY_MUTE: state = slot_MUTE; break; + case XRC_RFKEY_TV_PWR_TOGGLE: state = slot_TV_PWR_TOGGLE; break; + case XRC_RFKEY_TV_PWR_ON: state = slot_TV_PWR_ON; break; + case XRC_RFKEY_TV_PWR_OFF: state = slot_TV_PWR_OFF; break; + case XRC_RFKEY_AVR_PWR_TOGGLE: state = slot_AVR_PWR_TOGGLE; break; + case XRC_RFKEY_AVR_PWR_OFF: state = slot_AVR_PWR_OFF; break; + case XRC_RFKEY_AVR_PWR_ON: state = slot_AVR_PWR_ON; break; + case MSO_RFKEY_PWR_TOGGLE: state = slot_PWR_TOGGLE; break; + case MSO_RFKEY_PWR_OFF: state = slot_PWR_OFF; break; + case MSO_RFKEY_PWR_ON: state = slot_PWR_ON; break; + } + return state; + } +}; + +// Class to represent a single instance of a keyActionMap +class keyActionMap +{ +public: + int keyName; + int rfKeyCode; + byte_vector_t tvIRData; + byte_vector_t avrIRData; +}; + + +namespace WPEFramework { + + namespace Plugin { + + // Helper class for the RemoteActionMapping plugin - contains details of IARM communication with ControlMgr + class RemoteActionMappingHelper + { + public: + int getLastUsedDeviceID(std::string& remoteType, bool& bFiveDigitCodeSet, bool& bFiveDigitCodeSupported); + bool getControllerByID(int deviceID, std::string& remoteType, bool& pbFiveDigitCodeSet, bool& pbFiveDigitCodeSupported); + bool setKeyActionMap(int deviceID, int keymapType, keyActionMap& actionMap, const KeyGroupSrcInfo& srcInfo); + bool getKeyActionMap(int deviceID, int keymapType, keyActionMap& actionMap); // In this case, actionMap is an in/out parameter + bool clearKeyActionMap(int deviceID, int keymapType, int keyName); + bool setFiveDigitCode(int deviceID, int tvFiveDigitCode, int avrFiveDigitCode); + + bool setIRDBDownloadFlag(int deviceID, bool bDownload); + bool setFiveDigitCodeFlags(int deviceID, int mode); + bool cancelCodeDownload(int deviceID); + + bool getControllerLoadStatus(int deviceID, unsigned& tvLoadStatus, unsigned& avrLoadStatus); + + int lookupRFKey(int keyName); + int lookupKeyname(int rfKey); + + bool setDevicePower(int deviceID, int keymapType, keyActionMap& actionMap); + bool clearDevicePower(int deviceID, int keymapType, int rfKeyCode); + + private: + ctrlm_network_id_t getRf4ceNetworkID(void); + bool getRf4ceBindRemotes(rf4ceBindRemotes_t* bindRemotes); + bool setRIBDevicePower(int deviceID, int keymapType, int rfKeyCode, byte_vector_t& irData); + bool clearRIBDevicePower(int deviceID, int keymapType, int rfKeyCode); + }; + + } // namespace Plugin + +} // namespace WPEFramework diff --git a/RemoteActionMapping/RemoteActionMapping.config b/RemoteActionMapping/RemoteActionMapping.config new file mode 100644 index 0000000000..0c2ce1b7f7 --- /dev/null +++ b/RemoteActionMapping/RemoteActionMapping.config @@ -0,0 +1,3 @@ +set (autostart true) +set (preconditions Platform) +set (callsign "org.rdk.RemoteActionMapping") diff --git a/RemoteActionMapping/RemoteActionMapping.cpp b/RemoteActionMapping/RemoteActionMapping.cpp new file mode 100644 index 0000000000..c559cbdd79 --- /dev/null +++ b/RemoteActionMapping/RemoteActionMapping.cpp @@ -0,0 +1,2310 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "RemoteActionMapping.h" +#include "libIBusDaemon.h" + +#include "utils.h" +#include + +const int supported_ked_keynames[] = +{ + KED_DISCRETE_POWER_ON, + KED_DISCRETE_POWER_STANDBY, + KED_POWER, + KED_VOLUMEUP, + KED_VOLUMEDOWN, + KED_MUTE, + KED_INPUTKEY +}; +const int supported_ked_keynames_size = sizeof(supported_ked_keynames) / sizeof(int); + +// In ascending RFKey code order +const int supported_irrfdb_slots[] = +{ + MSO_RFKEY_INPUT_SELECT, // Non-power original/legacy slots + MSO_RFKEY_VOL_PLUS, + MSO_RFKEY_VOL_MINUS, + MSO_RFKEY_MUTE, + + XRC_RFKEY_TV_PWR_TOGGLE, // 3 new TV power keycodes (XRC Profile update) + XRC_RFKEY_TV_PWR_ON, + XRC_RFKEY_TV_PWR_OFF, + + XRC_RFKEY_AVR_PWR_TOGGLE, // 3 new AVR power keycodes (XRC Profile update) + XRC_RFKEY_AVR_PWR_OFF, + XRC_RFKEY_AVR_PWR_ON, + + MSO_RFKEY_PWR_TOGGLE, // Original/legacy power slots + MSO_RFKEY_PWR_OFF, + MSO_RFKEY_PWR_ON +}; +const int supported_irrfdb_slots_size = sizeof(supported_irrfdb_slots) / sizeof(int); + +// Encodings for "loadStatus" member in the onIRCodeLoad event +#define IRCODE_LOAD_STATUS_OK 0 +#define IRCODE_LOAD_STATUS_TIMEOUT_COMPLETE 1 +#define IRCODE_LOAD_STATUS_TIMEOUT_INCOMPLETE 2 +#define IRCODE_LOAD_STATUS_REFUSED 3 + +// "param" had better be a JsonArray! +#define getArrayParameter(paramName, param) {\ + if (Core::JSON::Variant::type::ARRAY == parameters[paramName].Content()) \ + param = parameters[paramName].Array();\ +} +#define getArrayParameterObject(parameters, paramName, param) {\ + if (Core::JSON::Variant::type::ARRAY == parameters[paramName].Content()) \ + param = parameters[paramName].Array();\ +} + +using namespace std; + +namespace WPEFramework { + + namespace Plugin { + + SERVICE_REGISTRATION(RemoteActionMapping, 1, 0); + + RemoteActionMapping* RemoteActionMapping::_instance = nullptr; + + static Core::TimerType ribLoadTimer(RAM_TIMER_THREAD_STACK_SIZE, RAM_TIMER_THREAD_NAME); + + IRRFDBCtrlrLoadProgress RemoteActionMapping::m_readProgress; + IRDBLoadState RemoteActionMapping::m_irdbLoadState = IRDB_LOAD_STATE_NONE; + int RemoteActionMapping::m_lastSetRemoteID = -1; + int RemoteActionMapping::m_ramsOperatingMode = RAMS_OP_MODE_NONE; + int RemoteActionMapping::m_fiveDigitCodeMode = FIVE_DIGIT_CODE_MODE_NONE; + bool RemoteActionMapping::m_lastSetHas5DCPresent = false; + bool RemoteActionMapping::m_lastSetSupports5DC = false; + + + RemoteActionMapping::RemoteActionMapping() + : AbstractPlugin() + , m_apiVersionNumber((uint32_t)-1) /* default max uint32_t so everything gets enabled */ //TODO(MROLLINS) Can't we access this from jsonrpc interface? + , m_ribLoadTimeoutImpl(this) + { + LOGINFO("ctor"); + RemoteActionMapping::_instance = this; + + registerMethod("getApiVersionNumber", &RemoteActionMapping::getApiVersionNumber, this); + + registerMethod("getLastUsedDeviceID", &RemoteActionMapping::getLastUsedDeviceIDWrapper, this); + registerMethod("getKeymap", &RemoteActionMapping::getKeymapWrapper, this); + registerMethod("setKeyActionMapping", &RemoteActionMapping::setKeyActionMappingWrapper, this); + registerMethod("clearKeyActionMapping", &RemoteActionMapping::clearKeyActionMappingWrapper, this); + registerMethod("getFullKeyActionMapping", &RemoteActionMapping::getFullKeyActionMappingWrapper, this); + registerMethod("getSingleKeyActionMapping", &RemoteActionMapping::getSingleKeyActionMappingWrapper, this); + registerMethod("cancelCodeDownload", &RemoteActionMapping::cancelCodeDownloadWrapper, this); + registerMethod("setFiveDigitCode", &RemoteActionMapping::setFiveDigitCodeWrapper, this); + + setApiVersionNumber(3); + } + + RemoteActionMapping::~RemoteActionMapping() + { + LOGINFO("dtor"); + RemoteActionMapping::_instance = nullptr; + + } + + const string RemoteActionMapping::Initialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + InitializeIARM(); + // On success return empty, to indicate there is no error text. + return (string()); + } + + void RemoteActionMapping::Deinitialize(PluginHost::IShell* /* service */) + { + LOGINFO(); + DeinitializeIARM(); + } + + void RemoteActionMapping::InitializeIARM() + { + LOGINFO(); + + if (Utils::IARM::init()) + { + IARM_Result_t res; + IARM_CHECK( IARM_Bus_RegisterEventHandler(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_EVENT_RIB_ACCESS_CONTROLLER, ramEventHandler) ); + } + } + + void RemoteActionMapping::DeinitializeIARM() + { + LOGINFO(); + + if (Utils::IARM::isConnected()) + { + IARM_Result_t res; + IARM_CHECK( IARM_Bus_RemoveEventHandler(CTRLM_MAIN_IARM_BUS_NAME, CTRLM_RCU_IARM_EVENT_RIB_ACCESS_CONTROLLER, ramEventHandler) ); + } + } + + // Begin event handlers + void RemoteActionMapping::ramEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + if (RemoteActionMapping::_instance) + RemoteActionMapping::_instance->iarmEventHandler(owner, eventId, data, len); + else + LOGWARN("WARNING - cannot handle IARM events without a RemoteActionMapping plugin instance!"); + } + + void RemoteActionMapping::iarmEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len) + { + LOGINFO(); + if (!strcmp(owner, CTRLM_MAIN_IARM_BUS_NAME)) + { + if (eventId == CTRLM_RCU_IARM_EVENT_RIB_ACCESS_CONTROLLER) + { + if (data != NULL) + { + ctrlm_rcu_iarm_event_rib_entry_access_t *ribEntry = (ctrlm_rcu_iarm_event_rib_entry_access_t*)data; + if (ribEntry->api_revision == CTRLM_RCU_IARM_BUS_API_REVISION) + { + int remoteId = (unsigned char)ribEntry->controller_id; + int networkId = (unsigned char)ribEntry->network_id; + int attrId = (unsigned char)ribEntry->identifier; + int index = (unsigned char)ribEntry->index; + int accessType = (unsigned char)ribEntry->access_type; + LOGINFO("RIB Access Event: network_id: %u, controller_id: %d, identifier: 0x%02X, index: 0x%02X, access_type: %s.", + networkId, remoteId, attrId, index, ((accessType > 1) ? "INVALID" : ((accessType == 0) ? "READ" : "WRITE"))); + + std::lock_guard guard(m_stateMutex); + + if (m_ramsOperatingMode == RAMS_OP_MODE_IRRF_DATABASE) + { + // Actions to take, for IRRF_DATABASE operational mode + if (accessType == CTRLM_ACCESS_TYPE_READ) + { + if ((attrId == CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_WRITTEN)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("target remote has read the IRRF Status Flags!"); + // Change state to FLAG_READ. Start the download progress timeout, + // and let the IRDB Download flag remain set until we see either + // some actual load progress, or a timeout. + m_irdbLoadState = IRDB_LOAD_STATE_FLAG_READ; + startRIBLoadTimer(TIMEOUT_REMOTE_IRRFDB_READ_IRCODE_START); + } + else + { + LOGWARN("unexpected IRRF Status Flags read by remoteId: %d!", remoteId); + } + } + else if ((attrId == CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE) && + (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("First IRRF Database read for target remote!"); + // This is the very first time we see the actual IRDB data being read by the remote. + m_irdbLoadState = IRDB_LOAD_STATE_PROGRESS; + // Clear the IRDB Download flag (back to default), to restore normal OK key operation. + m_helper.setIRDBDownloadFlag(remoteId, false); + + handleIRRFDBKeyRead(remoteId, index); + if (m_irdbLoadState == IRDB_LOAD_STATE_PROGRESS) + { + startRIBLoadTimer(TIMEOUT_REMOTE_IRRFDB_READ_IRCODE_NEXT); + } + } + else + { + LOGWARN("unexpected initial IRRF Database read by remoteId: %d!", remoteId); + } + } + else if ((attrId == CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE) && + (m_irdbLoadState == IRDB_LOAD_STATE_PROGRESS)) + { + if (remoteId == m_lastSetRemoteID) + { + handleIRRFDBKeyRead(remoteId, index); + if (m_irdbLoadState == IRDB_LOAD_STATE_PROGRESS) + { + startRIBLoadTimer(TIMEOUT_REMOTE_IRRFDB_READ_IRCODE_NEXT); + } + } + else + { + LOGWARN("unexpected IRRF Database read by remoteId: %d!", remoteId); + } + } + } + } + else if ((m_ramsOperatingMode == RAMS_OP_MODE_FIVE_DIGIT_CODE) && + ((m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_TV_SET) || + (m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_AVR_SET) || + (m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_TVAVR_SET))) + { + // Actions to take, for FIVE_DIGIT_CODE SET mode + if (accessType == CTRLM_ACCESS_TYPE_READ) + { + if ((attrId == CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_WRITTEN)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("target remote has read the IRRF Status Flags!"); + // Change state to FLAG_READ. Start the Target IRDB Status read timeout, + // and let whatever 5-Digit Code flag(s) remain set until we see either + // the Target IRDB Status read, or a timeout. + m_irdbLoadState = IRDB_LOAD_STATE_FLAG_READ; + startRIBLoadTimer(TIMEOUT_REMOTE_FIVEDIGITCODE_READ_TARGET); + } + else + { + LOGWARN("unexpected IRRF Status Flags read by remoteId: %d!", remoteId); + } + } + else if ((attrId == CTRLM_RCU_RIB_ATTR_ID_TARGET_IRDB_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("5-Digit Codes read from Target IRDB Status!"); + // This is the very first time we see the actual 5-Digit Codes being read by the remote. + m_irdbLoadState = IRDB_LOAD_STATE_TARGET_READ; + // Clear the 5-Digit Code flag(s) (back to default), to restore normal OK key operation. + m_helper.setFiveDigitCodeFlags(remoteId, FIVE_DIGIT_CODE_MODE_NONE); + + startRIBLoadTimer(TIMEOUT_REMOTE_FIVEDIGITCODE_WRITE_CONTROLLER); + } + else + { + LOGWARN("unexpected Target IRDB Status read by remoteId: %d!", remoteId); + } + } + } + else + { + // accessType == CTRLM_ACCESS_TYPE_WRITE + if ((attrId == CTRLM_RCU_RIB_ATTR_ID_CONTROLLER_IRDB_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_TARGET_READ)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("Controller IRDB Status updated!"); + m_irdbLoadState = IRDB_LOAD_STATE_CTRLR_WRITTEN; + handleFiveDigitCodeAccess(remoteId); + } + else + { + LOGWARN("unexpected Controller IRDB Status write by remoteId: %d!", remoteId); + } + } + } + } + else if ((m_ramsOperatingMode == RAMS_OP_MODE_FIVE_DIGIT_CODE) && + (m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_CLEAR)) + { + // Actions to take, for FIVE_DIGIT_CODE CLEAR mode + if (accessType == CTRLM_ACCESS_TYPE_READ) + { + if ((attrId == CTRLM_RCU_RIB_ATTR_ID_IR_RF_DATABASE_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_WRITTEN)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("target remote has read the IRRF Status Flags!"); + // Change state to FLAG_READ. Start the Controller IRDB Status write timeout, + // and let the 5-Digit Code Clear All flag remain set until we see either + // the Controller IRDB Status write, or a timeout. + m_irdbLoadState = IRDB_LOAD_STATE_FLAG_READ; + startRIBLoadTimer(TIMEOUT_REMOTE_FIVEDIGITCODE_WRITE_CONTROLLER); + } + else + { + LOGWARN("unexpected IRRF Status Flags read by remoteId: %d!", remoteId); + } + } + } + else + { + // accessType == CTRLM_ACCESS_TYPE_WRITE + if ((attrId == CTRLM_RCU_RIB_ATTR_ID_CONTROLLER_IRDB_STATUS) && + (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ)) + { + if (remoteId == m_lastSetRemoteID) + { + LOGWARN("Controller IRDB Status updated!"); + m_irdbLoadState = IRDB_LOAD_STATE_CTRLR_WRITTEN; + // Clear the 5-Digit Code flag(s) (back to default), to restore normal OK key operation. + m_helper.setFiveDigitCodeFlags(remoteId, FIVE_DIGIT_CODE_MODE_NONE); + + handleFiveDigitCodeAccess(remoteId); + } + else + { + LOGWARN("unexpected Controller IRDB Status write by remoteId: %d!", remoteId); + } + } + } + } + else + { + // _ramsOperatingMode == RAMS_OP_MODE_NONE + // Just ignore these events, but log them (already done far above). + LOGWARN("Event ignored - m_ramsOperatingMode is %d.", m_ramsOperatingMode); + } + } + else + { + LOGERR("ERROR - controlMgr event API version is %d, expected %d!!", + ribEntry->api_revision, CTRLM_RCU_IARM_BUS_API_REVISION); + } + } + else + { + LOGERR("ERROR - event data is NULL!"); + } + } + else + { + LOGERR("UNKNOWN controlMgr Event: eventId: %d", (int)eventId); + } + } + else + { + LOGERR("UNKNOWN EVENT - owner: %s, eventId: %d", owner, (int)eventId); + } + } // end of iarmEventHandler() + // End event handlers + + + // Begin methods + uint32_t RemoteActionMapping::getApiVersionNumber(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + response["version"] = m_apiVersionNumber; + + returnResponse(true); + } + + uint32_t RemoteActionMapping::getLastUsedDeviceIDWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + string remoteType; + int deviceID; + bool bFiveDigitCodeSet = false; + bool bFiveDigitCodeSupported = false; + + deviceID = getLastUsedDeviceID(remoteType, bFiveDigitCodeSet, bFiveDigitCodeSupported); + if (deviceID > 0) + { + response["deviceID"] = JsonValue(deviceID); + response["remoteType"] = remoteType; + response["fiveDigitCodePresent"] = JsonValue(bFiveDigitCodeSet); + response["setFiveDigitCodeSupported"] = JsonValue(bFiveDigitCodeSupported); + } else { + LOGERR("ERROR: getLastUsedDeviceID returned bad deviceID: %d!\n", deviceID); + status_code = STATUS_FAILURE; + } + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + + uint32_t RemoteActionMapping::getKeymapWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int keymapType = -1; + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || !parameters.HasLabel("keymapType")) + { + // There are either NO parameters, or no deviceID, or no keymapType. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID' and a 'keymapType' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + } + // Get the keymapType from the parameters + paramKey = "keymapType"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 1)) + { + // The keymapType value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keymapType' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keymapType = value; + LOGINFO("keymapType passed in is %d.", keymapType); + } + + response["keyNames"] = getKeymap(deviceID, keymapType); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + + uint32_t RemoteActionMapping::setKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int keymapType = -1; + int numMaps = 0; + KeyPresenceFlags keysPresent; + KeyGroupSrcInfo srcInfo; + JObjectArray keyActionMapList; + std::map localMaps; + + // Check for attempts to interrupt sequences that are already in-progress + { + std::lock_guard guard(m_stateMutex); + + if ((m_ramsOperatingMode != RAMS_OP_MODE_NONE) && (m_irdbLoadState > IRDB_LOAD_STATE_WRITTEN)) + { + LOGERR("ERROR - Operation in progress - OperatingMode: %d, LoadState: %d!", + m_ramsOperatingMode, m_irdbLoadState); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + } + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || + !parameters.HasLabel("keymapType") || !parameters.HasLabel("keyActionMapping")) + { + // There are either NO parameters, or no 'deviceID', no 'keymapType'. or no 'keyActionMapping'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID', a 'keymapType', and a 'keyActionMapping' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + // Check to see if this remote will accept this operation. + { + bool bHas5DCPresent = false; + bool bSupports5DC = false; + string remoteType; + // Get the controller's RAMS-related status + if (!m_helper.getControllerByID(deviceID, remoteType, bHas5DCPresent, bSupports5DC)) + { + LOGERR("ERROR - Can't get controller(%d) info.", deviceID); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // If the controller involved has a 5-Digit Code already set, + // meaning this method can't succeed. Fail now. + if (bHas5DCPresent) + { + LOGERR("ERROR - controller(%d) has a 5-Digit Code set, which blocks this method!!", deviceID); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + else + { + // Save the 5-Digit Code booleans as state information. + std::lock_guard guard(m_stateMutex); + m_lastSetHas5DCPresent = bHas5DCPresent; + m_lastSetSupports5DC = bSupports5DC; + } + } + } + // Get the keymapType from the parameters + paramKey = "keymapType"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 1)) + { + // The keymapType value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keymapType' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keymapType = value; + LOGINFO("keymapType passed in is %d.", keymapType); + } + // Get the keyActionMapping array from the parameters + paramKey = "keyActionMapping"; + if (parameters.HasLabel(paramKey)) + { + JsonArray value; + getArrayParameter(paramKey, value); + if ((value.Length() < 1) || (value.Length() > 7)) + { + // The keyActionMapping array size is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keyActionMapping' array parameter value - size is %d!", value.Length()); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keyActionMapList = value; + numMaps = (int)keyActionMapList.Length(); + LOGINFO("size of keyActionMapping array passed in is %d.", numMaps); + } + // Extract and convert the JsonObjects in the array to the keyActionMap class + if (numMaps > 0) + { + for (int i = 0; i < numMaps; i++) + { + keyActionMap actionMap; + JsonObject jsonActionMap; + int irCodeCount = 0; + + try + { + // Get a single JSON keyActionMap from the array of them + jsonActionMap = keyActionMapList[i]; + } + catch (...) + { + LOGERR("ERROR - exception converting keyActionMapList[%d] to object!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // Get the keyName + paramKey = "keyName"; + if (!jsonActionMap.HasLabel(paramKey)) + { + LOGERR("ERROR - keyActionMap[%d]: missing keyName parameter!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + int value = 0; + getNumberParameterObject(jsonActionMap, paramKey, value); + if ((value < 0) || (value > 255)) + { + // Value is out of range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad jsonActionMap 'keyName' value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + actionMap.keyName = value; + LOGWARN("keyActionMap[%d]: keyName value: 0x%02X.", i, actionMap.keyName); + } + // Get the rfKeyCode + paramKey = "rfKeyCode"; + if (!jsonActionMap.HasLabel(paramKey)) + { + LOGERR("ERROR - keyActionMap[%d]: missing rfKeyCode parameter!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + int value = 0; + getNumberParameterObject(jsonActionMap, paramKey, value); + // Lookup the rfKeyCode ourselves, using the keyName value + actionMap.rfKeyCode = m_helper.lookupRFKey(actionMap.keyName); + if ((actionMap.rfKeyCode < 0) || (actionMap.rfKeyCode > 255)) + { + // rfKeyCode is out of range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad lookup 'rfKeyCode' value: %d!", actionMap.rfKeyCode); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + LOGWARN("keyActionMap[%d]: rfKeyCode value: 0x%02X, input rfKeyCode: 0x%02X.", i, actionMap.rfKeyCode, value); + } + + // Get the tvIRKeyCode IR waveform data + paramKey = "tvIRKeyCode"; + if (!jsonActionMap.HasLabel(paramKey)) + { + LOGERR("ERROR - keyActionMap[%d]: missing tvIRKeyCode parameter!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + JsonArray irData; + getArrayParameterObject(jsonActionMap, paramKey, irData); + if (irData.Length() > CONTROLMGR_MAX_IR_DATA_SIZE) + { + // The tvIRKeyCode array size is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'tvIRKeyCode' array parameter value - size is %d!", irData.Length()); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + LOGWARN("keyActionMap[%d]: tvIRKeyCode size: %d.", i, (int)irData.Length()); + + actionMap.tvIRData.clear(); + if (irData.Length() > 0) + { + LOGWARN("keyActionMap[%d]: tvIRKeyCode element type: %d.", i, (int)irData[0].Content()); + // Load the byte vector from the number JsonArray + for (int j = 0; j < irData.Length(); j++) + { + int byteval; + try + { + byteval = irData[j].Number(); + } + catch (...) + { + LOGERR("ERROR - exception converting tvIRKeyCode irData[%d] to number!", j); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + if ((byteval < 0) || (byteval > 255)) + { + LOGERR("ERROR - tvIRKeyCode data byteval %d is out of range!", byteval); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + actionMap.tvIRData.push_back((unsigned char)(byteval & 0xFF)); + } + keysPresent.tv.setKey(actionMap.rfKeyCode); + LOGWARN("keyActionMap[%d]: tvIRKeyCode size: %d.", i, actionMap.tvIRData.size()); + if (actionMap.tvIRData.size() > 12) + { + LOGWARN("tvIRKeyCode bytes - 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X", + (unsigned char)actionMap.tvIRData.at(0), (unsigned char)actionMap.tvIRData.at(1), (unsigned char)actionMap.tvIRData.at(2), + (unsigned char)actionMap.tvIRData.at(3), (unsigned char)actionMap.tvIRData.at(4), (unsigned char)actionMap.tvIRData.at(5), + (unsigned char)actionMap.tvIRData.at(6), (unsigned char)actionMap.tvIRData.at(7), (unsigned char)actionMap.tvIRData.at(8), + (unsigned char)actionMap.tvIRData.at(9), (unsigned char)actionMap.tvIRData.at(10), (unsigned char)actionMap.tvIRData.at(11)); + } + irCodeCount++; + } + } + + // Get the avrIRKeyCode IR waveform data + paramKey = "avrIRKeyCode"; + if (!jsonActionMap.HasLabel(paramKey)) + { + LOGERR("ERROR - keyActionMap[%d]: missing avrIRKeyCode parameter!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + JsonArray irData; + getArrayParameterObject(jsonActionMap, paramKey, irData); + if (irData.Length() > CONTROLMGR_MAX_IR_DATA_SIZE) + { + // The avrIRKeyCode array size is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'avrIRKeyCode' array parameter value - size is %d!", irData.Length()); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + LOGWARN("keyActionMap[%d]: avrIRKeyCode size: %d.", i, (int)irData.Length()); + + actionMap.avrIRData.clear(); + if (irData.Length() > 0) + { + LOGWARN("keyActionMap[%d]: avrIRKeyCode element type: %d.", i, (int)irData[0].Content()); + // Load the byte vector from the number JsonArray + for (int j = 0; j < irData.Length(); j++) + { + int byteval; + try + { + byteval = irData[j].Number(); + } + catch (...) + { + LOGERR("ERROR - exception converting avrIRKeyCode irData[%d] to number!", j); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + if ((byteval < 0) || (byteval > 255)) + { + LOGERR("ERROR - avrIRKeyCode data byteval %d is out of range!", byteval); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + actionMap.avrIRData.push_back((unsigned char)(byteval & 0xFF)); + } + keysPresent.avr.setKey(actionMap.rfKeyCode); + LOGWARN("keyActionMap[%d]: avrIRKeyCode size: %d.", i, actionMap.avrIRData.size()); + if (actionMap.avrIRData.size() > 12) + { + LOGWARN("avrIRKeyCode bytes - 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X", + (unsigned char)actionMap.avrIRData.at(0), (unsigned char)actionMap.avrIRData.at(1), (unsigned char)actionMap.avrIRData.at(2), + (unsigned char)actionMap.avrIRData.at(3), (unsigned char)actionMap.avrIRData.at(4), (unsigned char)actionMap.avrIRData.at(5), + (unsigned char)actionMap.avrIRData.at(6), (unsigned char)actionMap.avrIRData.at(7), (unsigned char)actionMap.avrIRData.at(8), + (unsigned char)actionMap.avrIRData.at(9), (unsigned char)actionMap.avrIRData.at(10), (unsigned char)actionMap.avrIRData.at(11)); + } + irCodeCount++; + } + } + + if (irCodeCount < 1) + { + LOGERR("ERROR - keyActionMap[%d]: empty tvIRKeyCode AND empty avrIRKeyCode!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // Store the keyActionMap in the local map. Use automatic allocation in the map, so there are no leaks. + localMaps[actionMap.rfKeyCode] = actionMap; + } + LOGWARN("localMaps is finished, size: %d", (int)localMaps.size()); + if (numMaps != (int)localMaps.size()) + { + LOGERR("ERROR - maps conversion failure - input numMaps: %d, output localMaps size: %d!", + numMaps, localMaps.size()); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + // Also check the localMaps for coherence, before we try to use them. + int count = 0; + for (int i = 0; i < supported_ked_keynames_size; i++) + { + int rfKeyCode = m_helper.lookupRFKey(supported_ked_keynames[i]); + bool bHasMap = true; + keyActionMap actionMap; + + try + { + actionMap = localMaps.at(rfKeyCode); + } + catch (...) + { + LOGWARN("WARNING - No localMaps entry for rfKeyCode: %d", rfKeyCode); + bHasMap = false; + } + if (bHasMap) + { + if (actionMap.rfKeyCode != rfKeyCode) + { + LOGERR("ERROR - For rfKeyCode 0x%02X, found actionMap with rfKeyCode 0x%02X!", + rfKeyCode, actionMap.rfKeyCode); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + if ((actionMap.tvIRData.size() <= 1) && (actionMap.avrIRData.size() <= 1)) + { + LOGERR("ERROR - For rfKeyCode 0x%02X, found actionMap with no IR data!", rfKeyCode); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + count++; + } + } + if (numMaps != count) + { + LOGERR("ERROR - localMaps count wrong - input numMaps: %d, output localMaps count: %d!", + numMaps, count); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + } + } + + // Decide on the TV/AVR key group assignments, based on the 7 original IR codesets present + if (!setKeyGroups(srcInfo, const_cast(keysPresent))) + { + LOGERR("ERROR - incoherent IR codesets!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + + // Modify the IRRF database entries + status_code = (setKeyActionMapping(deviceID, keymapType, localMaps, const_cast(srcInfo)) ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } // end of setKeyActionMappingWrapper() + + + uint32_t RemoteActionMapping::clearKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int keymapType = -1; + int keyNames[supported_ked_keynames_size]; + int numNames = 0; + JsonArray keyNamesList; + RFKeyFlags rfKeyFlags; + + // Check for attempts to interrupt sequences that are already in-progress + { + std::lock_guard guard(m_stateMutex); + + if ((m_ramsOperatingMode != RAMS_OP_MODE_NONE) && (m_irdbLoadState > IRDB_LOAD_STATE_WRITTEN)) + { + LOGERR("ERROR - Operation in progress - OperatingMode: %d, LoadState: %d!", + m_ramsOperatingMode, m_irdbLoadState); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + } + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || + !parameters.HasLabel("keymapType") || !parameters.HasLabel("keyNames")) + { + // There are either NO parameters, or no 'deviceID', no 'keymapType'. or no 'keyNames'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID', a 'keymapType', and a 'keyNames' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + // Check to see if this remote will accept this operation. + { + bool bHas5DCPresent = false; + bool bSupports5DC = false; + string remoteType; + // Get the controller's RAMS-related status + if (!m_helper.getControllerByID(deviceID, remoteType, bHas5DCPresent, bSupports5DC)) + { + LOGERR("ERROR - Can't get controller(%d) info.", deviceID); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // If the controller involved has a 5-Digit Code already set, + // meaning this method can't succeed. Fail now. + if (bHas5DCPresent) + { + LOGERR("ERROR - controller(%d) has a 5-Digit Code set, which blocks this method!!", deviceID); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + else + { + // Save the 5-Digit Code booleans as state information. + std::lock_guard guard(m_stateMutex); + m_lastSetHas5DCPresent = bHas5DCPresent; + m_lastSetSupports5DC = bSupports5DC; + } + } + } + // Get the keymapType from the parameters + paramKey = "keymapType"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 1)) + { + // The keymapType value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keymapType' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keymapType = value; + LOGINFO("keymapType passed in is %d.", keymapType); + } + // Get the keyNames array from the parameters + paramKey = "keyNames"; + if (parameters.HasLabel(paramKey)) + { + JsonArray value; + getArrayParameter(paramKey, value); + if ((value.Length() < 1) || (value.Length() > supported_ked_keynames_size)) + { + // The keyNames array size is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keyNames' array parameter value - size is %d!", value.Length()); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keyNamesList = value; + numNames = (int)keyNamesList.Length(); + LOGINFO("size of keyNames array passed in is %d.", numNames); + LOGWARN("keyNamesList element type: %d.", (int)keyNamesList[0].Content()); + } + if (numNames > 0) + { + int rfKeyCode = -1; + memset((void*)keyNames, 0, sizeof(keyNames)); + for (int i = 0; i < numNames; i++) + { + try + { + keyNames[i] = keyNamesList[i].Number(); + } + catch (...) + { + LOGERR("ERROR - exception converting keyNamesList[%d] to number!", i); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + // Check keyName validity, and build keyflags + rfKeyCode = m_helper.lookupRFKey(keyNames[i]); + if (rfKeyCode < 0) + { + LOGERR("ERROR - invalid keyName value: 0x%02X!", keyNames[i]); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + if (rfKeyFlags.isSet(rfKeyCode)) + { + LOGERR("ERROR - duplicate keyName: 0x%02X!", keyNames[i]); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + else + { + rfKeyFlags.setKey(rfKeyCode); + LOGWARN("keyNames value at %d is: 0x%08X.", i, keyNames[i]); + } + } + } + } + + // Ensure that keynames groups are coherent. + if (!checkClearList(rfKeyFlags, keyNames, &numNames)) + { + LOGERR("ERROR - incoherent keyNames!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + status_code = (clearKeyActionMapping(deviceID, keymapType, keyNames, numNames) ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } // end of clearKeyActionMappingWrapper + + uint32_t RemoteActionMapping::getFullKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int keymapType = -1; + JsonArray mappings; + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || !parameters.HasLabel("keymapType")) + { + // There are either NO parameters, or no 'deviceID', or 'keymapType'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID' and 'keymapType' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + } + // Get the keymapType from the parameters + paramKey = "keymapType"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 1)) + { + // The keymapType value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keymapType' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keymapType = value; + LOGINFO("keymapType passed in is %d.", keymapType); + } + + mappings = getFullKeyActionMapping(deviceID, keymapType); + response["keyMappings"] = JsonValue(mappings); + + status_code = ((mappings.Length() > 0) ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + + uint32_t RemoteActionMapping::getSingleKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int keymapType = -1; + int keyName = -1; + JsonObject mapping; + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || + !parameters.HasLabel("keymapType") || !parameters.HasLabel("keyName")) + { + // There are either NO parameters, or no 'deviceID', 'keymapType', or 'keyName'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID', 'keymapType', and 'keyName' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + } + // Get the keymapType from the parameters + paramKey = "keymapType"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 1)) + { + // The keymapType value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keymapType' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keymapType = value; + LOGINFO("keymapType passed in is %d.", keymapType); + } + // Get the keyName from the parameters + paramKey = "keyName"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if (value <= 0) + { + // The keyName value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'keyName' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + keyName = value; + LOGINFO("keyName passed in is %d.", keyName); + } + + mapping = getSingleKeyActionMapping(deviceID, keymapType, keyName); + response["keyMapping"] = JsonValue(mapping); + + status_code = (mapping.IsSet() ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + + uint32_t RemoteActionMapping::cancelCodeDownloadWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + + // Check for attempts to cancel sequences that are already in-progress (i.e. too late, can't be canceled) + { + std::lock_guard guard(m_stateMutex); + + if ((m_ramsOperatingMode != RAMS_OP_MODE_NONE) && (m_irdbLoadState > IRDB_LOAD_STATE_WRITTEN)) + { + LOGERR("ERROR - Too late to cancel operation - OperatingMode: %d, LoadState: %d!", + m_ramsOperatingMode, m_irdbLoadState); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + } + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID")) + { + // There are either NO parameters, or no 'deviceID'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + } + + status_code = (cancelCodeDownload(deviceID) ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + + uint32_t RemoteActionMapping::setFiveDigitCodeWrapper(const JsonObject& parameters, JsonObject& response) + { + LOGINFOMETHOD(); + StatusCode status_code = STATUS_OK; + const char* paramKey = NULL; + int deviceID = -1; + int tvFiveDigitCode = -1; + int avrFiveDigitCode = -1; + + // Check for attempts to interrupt sequences that are already in-progress + { + std::lock_guard guard(m_stateMutex); + + if ((m_ramsOperatingMode != RAMS_OP_MODE_NONE) && (m_irdbLoadState > IRDB_LOAD_STATE_WRITTEN)) + { + LOGERR("ERROR - Operation in progress - OperatingMode: %d, LoadState: %d!", + m_ramsOperatingMode, m_irdbLoadState); + response["status_code"] = (int)STATUS_INVALID_STATE; + returnResponse(false); + } + } + + if (!parameters.IsSet() || !parameters.HasLabel("deviceID") || + !parameters.HasLabel("tvFiveDigitCode") || !parameters.HasLabel("avrFiveDigitCode")) + { + // There are either NO parameters, or no 'deviceID', no 'tvFiveDigitCode'. or no 'avrFiveDigitCode'. + // We will treat this as a fatal error. Exit now. + LOGERR("ERROR - this method requires a 'deviceID', a 'tvFiveDigitCode', and an avrFiveDigitCode' parameter!"); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + + // Get the deviceID from the parameters + paramKey = "deviceID"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value <= 0) || (value > CTRLM_MAIN_MAX_BOUND_CONTROLLERS)) + { + // The deviceID value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'deviceID' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + deviceID = value; + LOGINFO("deviceID passed in is %d.", deviceID); + } + // Get the tvFiveDigitCode from the parameters + paramKey = "tvFiveDigitCode"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 99999)) + { + // The tvFiveDigitCode value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'tvFiveDigitCode' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + tvFiveDigitCode = value; + LOGINFO("tvFiveDigitCode passed in is %d.", tvFiveDigitCode); + } + // Get the avrFiveDigitCode from the parameters + paramKey = "avrFiveDigitCode"; + if (parameters.HasLabel(paramKey)) + { + int value = 0; + getNumberParameter(paramKey, value); + if ((value < 0) || (value > 99999)) + { + // The avrFiveDigitCode value is not in range. We will treat this as a fatal error. Exit now. + LOGERR("ERROR - Bad 'avrFiveDigitCode' parameter value: %d!", value); + response["status_code"] = (int)STATUS_INVALID_ARGUMENT; + returnResponse(false); + } + avrFiveDigitCode = value; + LOGINFO("avrFiveDigitCode passed in is %d.", avrFiveDigitCode); + } + + status_code = (setFiveDigitCode(deviceID, tvFiveDigitCode, avrFiveDigitCode) ? STATUS_OK : STATUS_FAILURE); + + response["status_code"] = (int)status_code; + returnResponse(status_code == STATUS_OK); + } + // End methods + + + // Begin events + void RemoteActionMapping::onIRCodeLoad(int deviceID, int_vector_t& rfKeyCodes, int status) + { + JsonObject params; + JsonArray keyNames; + JsonArray keyCodes; + + // Build the KED keyNames list from the rfKeyCodes list + for (std::size_t i = 0; i < rfKeyCodes.size(); i++) + { + int name = m_helper.lookupKeyname(rfKeyCodes[i]); + + keyCodes.Add(JsonValue(rfKeyCodes[i])); + keyNames.Add(JsonValue(name)); + + LOGINFO("Sending rfKeyCode: 0x%02X, keyname: 0x%02X.", + (unsigned)rfKeyCodes[i], (unsigned)name); + } + + params["deviceID"] = JsonValue(deviceID); + params["keyNames"] = keyNames; + params["rfKeyCodes"] = keyCodes; + params["loadStatus"] = JsonValue(status); + + sendNotify("onIRCodeLoad", params); + } + + void RemoteActionMapping::onFiveDigitCodeLoad(int deviceID, int tvStatus, int avrStatus) + { + JsonObject params; + + LOGINFO("Sending deviceID: %d, tvStatus: 0x%02X, avrStatus: 0x%02X.", + deviceID, tvStatus, avrStatus); + + params["deviceID"] = JsonValue(deviceID); + params["tvLoadStatus"] = JsonValue(tvStatus); + params["avrLoadStatus"] = JsonValue(avrStatus); + + sendNotify("onFiveDigitCodeLoad", params); + } + // End events + + + // Begin private method implementations + int RemoteActionMapping::getLastUsedDeviceID(std::string& remoteType, bool& bFiveDigitCodeSet, bool& bFiveDigitCodeSupported) + { + int deviceID = 1; + + deviceID = m_helper.getLastUsedDeviceID(remoteType, bFiveDigitCodeSet, bFiveDigitCodeSupported); + + return deviceID; + } + + JsonArray RemoteActionMapping::getKeymap(int deviceID, int keymapType) + { + UNUSED(deviceID); + UNUSED(keymapType); + + JsonArray keyNames; + + // Fill in the array of integer "KED_" values (hard-coded list) + for (int i = 0; i < supported_ked_keynames_size; i++) + { + keyNames.Add(JsonValue(supported_ked_keynames[i])); + } + + return keyNames; + } + + bool RemoteActionMapping::setKeyActionMapping(int deviceID, int keymapType, std::map& localActionMaps, const KeyGroupSrcInfo& srcInfo) + { + keyActionMap actionMap; + keyActionMap altActionMap; + int rfKeyCode = -1; + bool status = false; + bool success = true; + + // Create an "alternate" actionMap for the discrete power entries, if needed. + if ((srcInfo.groupDiscretePower == KEY_GROUP_SRC_TV_PWR_CROSS) || + (srcInfo.groupDiscretePower == KEY_GROUP_SRC_AVR_PWR_CROSS)) + { + // Use the toggle power IR data as the alternate. + bool bHasMap = true; + try + { + actionMap = localActionMaps.at(MSO_RFKEY_PWR_TOGGLE); + } + catch (...) + { + LOGERR("LOGIC ERROR - power crossover needed, but no toggle power actionMap available!!"); + bHasMap = false; + } + if (bHasMap) + { + altActionMap.keyName = KED_UNDEFINEDKEY; // Flag that the map is an alternate. + altActionMap.rfKeyCode = 0; // Set this to the on/off discrete code later + altActionMap.tvIRData = actionMap.tvIRData; + altActionMap.avrIRData = actionMap.avrIRData; + LOGWARN("Performing a power crossover."); + } + } + + // For every one of the original 7 keys, either set the RIB entry from the ActionMap, or clear the RIB entry. + // Use any power-related actionMaps that we got to independently set or clear the separate power RIB entries. + for (int i = 0; i < supported_ked_keynames_size; i++) + { + rfKeyCode = m_helper.lookupRFKey(supported_ked_keynames[i]); + if (rfKeyCode > 0) + { + bool bHasMap = true; + try + { + actionMap = localActionMaps.at(rfKeyCode); + } + catch (...) + { + bHasMap = false; + } + // Handle the separate device power IRRFDB entries + if ((rfKeyCode == MSO_RFKEY_PWR_ON) || (rfKeyCode == MSO_RFKEY_PWR_OFF) || (rfKeyCode == MSO_RFKEY_PWR_TOGGLE)) + { + if (bHasMap) + { + status = m_helper.setDevicePower(deviceID, keymapType, actionMap); + if (status) + { + LOGWARN("setDevicePower success for rfKeyCode: 0x%02X", rfKeyCode); + } + else + { + LOGERR("ERROR - separate power set failure for rfKeyCode: 0x%02X.", rfKeyCode); + success = false; + } + } + else + { + status = m_helper.clearDevicePower(deviceID, keymapType, rfKeyCode); + if (status) + { + LOGWARN("clearDevicePower success for rfKeyCode: 0x%02X", rfKeyCode); + } + else + { + LOGERR("ERROR - separate power clear failure for rfKeyCode: 0x%02X.", rfKeyCode); + success = false; + } + } + } + // Now take care of the original 7 IRRFDB entries. + // If we have decided to use an alternate source for the discrete power slots, + // disregard any actual discrete power actionMap that may exist, and use the alternate. + if ((altActionMap.keyName == KED_UNDEFINEDKEY) && + ((rfKeyCode == MSO_RFKEY_PWR_ON) || (rfKeyCode == MSO_RFKEY_PWR_OFF))) + { + // We MUST set alternate IR codes into the original discrete power slots, if we have them. + altActionMap.rfKeyCode = rfKeyCode; + status = m_helper.setKeyActionMap(deviceID, keymapType, altActionMap, srcInfo); + if (status) + { + LOGWARN("setKeyActionMap success with alternate for rfKeyCode: 0x%02X", rfKeyCode); + } + else + { + LOGERR("ERROR - alternate set failure for rfKeyCode: 0x%02X.", rfKeyCode); + success = false; + } + } + else + { + // Use the actionMaps to set or clear one of the the original 7 IRRFDB slots. + if (bHasMap) + { + // We have an ActionMap for this rfKeyCode, so we should be able to set the RIB from it. + status = m_helper.setKeyActionMap(deviceID, keymapType, actionMap, srcInfo); + if (status) + { + LOGWARN("setKeyActionMap success for rfKeyCode: 0x%02X", rfKeyCode); + } + else + { + LOGERR("ERROR - set failure for rfKeyCode: 0x%02X.", rfKeyCode); + success = false; + } + } + else + { + // We don't have an ActionMap for this rfKeyCode. Clear the IRRFDB RIB entry back to the "not programmed" state. + status = m_helper.clearKeyActionMap(deviceID, keymapType, supported_ked_keynames[i]); + if (status) + { + LOGWARN("Auto-cleared rfKeyCode 0x%02X", rfKeyCode); + } + else + { + LOGERR("ERROR - auto-clear failure at rfKeyCode: 0x%02X!", rfKeyCode); + success = false; + } + } + } + } + else + { + LOGERR("LOGIC ERROR - keyName 0x%02X has no matching rfKeyCode?!", supported_ked_keynames[i]); + } + } + + if (success) + { + { + std::lock_guard guard(m_stateMutex); + // Remember the ID of the remote that last changed the RIB key codes + m_lastSetRemoteID = deviceID; + if (m_irdbLoadState != IRDB_LOAD_STATE_NONE) + { + LOGWARN("WARNING - LoadState: %d, should be %d!", m_irdbLoadState, IRDB_LOAD_STATE_NONE); + } + if (m_ramsOperatingMode != RAMS_OP_MODE_NONE) + { + LOGWARN("WARNING - OperatingMode: %d, should be %d!", m_ramsOperatingMode, RAMS_OP_MODE_NONE); + } + m_irdbLoadState = IRDB_LOAD_STATE_WRITTEN; + m_ramsOperatingMode = RAMS_OP_MODE_IRRF_DATABASE; + } + // Now that the individual key codes are set in the RIB, + // enable Download from IRRFDB on OK Keypress mode, so the remote reads the RIB IRRF Database + m_helper.setIRDBDownloadFlag(deviceID, true); + // Initialize the controller load progress state. + m_readProgress.clear(); + } + + return success; + } // end of setKeyActionMapping() + + bool RemoteActionMapping::clearKeyActionMapping(int deviceID, int keymapType, int* keyNames, int numNames) + { + bool result = false; + int rfKeyCode = -1; + + if ((deviceID < 1) || (keyNames == NULL) || (numNames < 1)) + { + LOGERR("ERROR - Bad argument."); + return result; + } + + for (int i = 0; i < numNames; i++) + { + rfKeyCode = m_helper.lookupRFKey(keyNames[i]); + if (rfKeyCode < 0) + { + LOGERR("LOGIC ERROR - bad key lookup at keyName %d, index %d!", keyNames[i], i); + break; + } + result = m_helper.clearKeyActionMap(deviceID, keymapType, keyNames[i]); + if (!result) + { + LOGERR("ERROR - failure at keyName %d, index %d!", keyNames[i], i); + break; + } + } + + if (result) + { + // Set up state machine + { + std::lock_guard guard(m_stateMutex); + // Remember the ID of the remote that last changed the RIB key codes + m_lastSetRemoteID = deviceID; + if (m_irdbLoadState != IRDB_LOAD_STATE_NONE) + { + LOGWARN("WARNING - LoadState: %d, should be %d!", m_irdbLoadState, IRDB_LOAD_STATE_NONE); + } + if (m_ramsOperatingMode != RAMS_OP_MODE_NONE) + { + LOGWARN("WARNING - OperatingMode: %d, should be %d!", m_ramsOperatingMode, RAMS_OP_MODE_NONE); + } + m_irdbLoadState = IRDB_LOAD_STATE_WRITTEN; + m_ramsOperatingMode = RAMS_OP_MODE_IRRF_DATABASE; + } + // Now that the individual key codes are set in the RIB, + // enable Download from IRRDB on OK Keypress mode, so the remote reads the RIB IRRF Database + m_helper.setIRDBDownloadFlag(deviceID, true); + // Initialize the controller load progress state. + m_readProgress.clear(); + } + + return result; + } + + RemoteActionMapping::JObjectArray RemoteActionMapping::getFullKeyActionMapping(int deviceID, int keymapType) + { + UNUSED(keymapType); + JObjectArray keyMappings; + + if (deviceID < 1) + { + LOGERR("ERROR - Bad deviceID argument!"); + return keyMappings; + } + + for (int i = 0; i < supported_ked_keynames_size; i++) + { + JsonObject keyMapping; + keyActionMap actionMap; + actionMap.keyName = supported_ked_keynames[i]; + if (!m_helper.getKeyActionMap(deviceID, keymapType, actionMap)) + { + LOGERR("ERROR - bad keyName: 0x%02X.", actionMap.keyName); + break; + } + else + { + keyMapping["keyName"] = JsonValue(actionMap.keyName); + keyMapping["rfKeyCode"] = JsonValue(actionMap.rfKeyCode); + // Convert the IR data blobs from byte vectors to JsonArrays + JsonArray tvData; + for (std::size_t j = 0; j < actionMap.tvIRData.size(); j++) + { + tvData.Add(JsonValue((int)actionMap.tvIRData[j])); + } + keyMapping["tvIRKeyCode"] = JsonValue(tvData); + + JsonArray avrData; + for (std::size_t j = 0; j < actionMap.avrIRData.size(); j++) + { + avrData.Add(JsonValue((int)actionMap.avrIRData[j])); + } + keyMapping["avrIRKeyCode"] = JsonValue(avrData); + } + keyMappings.Add(keyMapping); + } + + return keyMappings; + } + + JsonObject RemoteActionMapping::getSingleKeyActionMapping(int deviceID, int keymapType, int keyName) + { + UNUSED(keymapType); + JsonObject keyMapping; + keyActionMap actionMap; + + if ((deviceID < 1) || (keyName < 0) || (keyName > 255)) + { + LOGERR("ERROR - Bad argument!"); + return keyMapping; + } + + actionMap.keyName = keyName; + if (!m_helper.getKeyActionMap(deviceID, keymapType, actionMap)) + { + LOGERR("ERROR - bad keyName: 0x%02X.", keyName); + } + else + { + // Return a single keyActionMap (as a JsonObject) + keyMapping["keyName"] = JsonValue(actionMap.keyName); + keyMapping["rfKeyCode"] = JsonValue(actionMap.rfKeyCode); + // Convert the IR data blobs from byte vectors to JsonArrays + JsonArray tvData; + for (std::size_t j = 0; j < actionMap.tvIRData.size(); j++) + { + tvData.Add(JsonValue((int)actionMap.tvIRData[j])); + } + keyMapping["tvIRKeyCode"] = JsonValue(tvData); + + JsonArray avrData; + for (std::size_t j = 0; j < actionMap.avrIRData.size(); j++) + { + avrData.Add(JsonValue((int)actionMap.avrIRData[j])); + } + keyMapping["avrIRKeyCode"] = JsonValue(avrData); + } + + return keyMapping; + } + + bool RemoteActionMapping::cancelCodeDownload(int deviceID) + { + if ((m_lastSetRemoteID > 0) && (m_lastSetRemoteID != deviceID)) + { + // FAILURE: Trying to cancel with a deviceID that doesn't match the deviceID we were primed with!?! + LOGERR("ERROR - deviceID: %d, doesn't match 'set' deviceID: %d!!", deviceID, m_lastSetRemoteID); + return false; + } + // Cancel whatever request is set in the IRRF Status Flags first, before the OK key gets pressed! + if (!m_helper.cancelCodeDownload(deviceID)) + { + LOGERR("ERROR - helper failure - bad deviceID: %d?", deviceID); + return false; + } + else + { + int opmode, loadstate; + { + std::lock_guard guard(m_stateMutex); + + // Then clean up our event-driven state machine, as well. + m_lastSetRemoteID = -1; + opmode = m_ramsOperatingMode; + loadstate = m_irdbLoadState; + m_irdbLoadState = IRDB_LOAD_STATE_NONE; + m_ramsOperatingMode = RAMS_OP_MODE_NONE; + m_fiveDigitCodeMode = FIVE_DIGIT_CODE_MODE_NONE; + } + + if (opmode == RAMS_OP_MODE_NONE) + { + LOGWARN("%s: WARNING - cancelCodeDownload called with NO operation to cancel! LoadState: %d.", + __FUNCTION__, loadstate); + } + else + { + LOGWARN("%s Download Cancelled: LoadState was %d!", + ((opmode == RAMS_OP_MODE_IRRF_DATABASE) ? "IRRF Database" : "5-Digit Code"), loadstate); + } + } + + return true; + } + + bool RemoteActionMapping::setFiveDigitCode(int deviceID, int tvFiveDigitCode, int avrFiveDigitCode) + { + int mode = 0; + bool success = m_helper.setFiveDigitCode(deviceID, tvFiveDigitCode, avrFiveDigitCode); + + if (success) + { + { + std::lock_guard guard(m_stateMutex); + + // Choose the enumerated FiveDigitCodeMode by testing the TV/AVR codes. + if ((tvFiveDigitCode == 0) && (avrFiveDigitCode == 0)) + { + mode = FIVE_DIGIT_CODE_MODE_CLEAR; + } + else if ((tvFiveDigitCode > 0) && (avrFiveDigitCode == 0)) + { + mode = FIVE_DIGIT_CODE_MODE_TV_SET; + } + else if ((tvFiveDigitCode == 0) && (avrFiveDigitCode > 0)) + { + mode = FIVE_DIGIT_CODE_MODE_AVR_SET; + } + else if ((tvFiveDigitCode > 0) && (avrFiveDigitCode > 0)) + { + mode = FIVE_DIGIT_CODE_MODE_TVAVR_SET; + } + + if (mode == 0) + { + LOGERR("LOGIC ERROR: 5-digit code mode set to NONE!?!"); + return false; + } + else + { + LOGWARN("5-digit code mode set to %d!", mode); + } + + // Remember the ID of the remote that last changed the 5-digit codes, and setup the state machine. + m_lastSetRemoteID = deviceID; + if (m_irdbLoadState != IRDB_LOAD_STATE_NONE) + { + LOGWARN("WARNING - LoadState: %d, should be %d!", m_irdbLoadState, IRDB_LOAD_STATE_NONE); + } + if (m_ramsOperatingMode != RAMS_OP_MODE_NONE) + { + LOGWARN("WARNING - OperatingMode: %d, should be %d!", m_ramsOperatingMode, RAMS_OP_MODE_NONE); + } + if (m_fiveDigitCodeMode != FIVE_DIGIT_CODE_MODE_NONE) + { + LOGWARN("WARNING - FiveDigitCodeMode: %d, should be %d!", m_ramsOperatingMode, FIVE_DIGIT_CODE_MODE_NONE); + } + m_irdbLoadState = IRDB_LOAD_STATE_WRITTEN; + m_ramsOperatingMode = RAMS_OP_MODE_FIVE_DIGIT_CODE; + m_fiveDigitCodeMode = mode; + } + // Now that the 5-digit codes are set in the RIB, and the state machine is set up, + // request Download from Target IRDB Status, or Clear All, on OK Keypress, so the remote sees the request + m_helper.setFiveDigitCodeFlags(deviceID, mode); + } + + return true; + } + // End private method implementations + + + // Begin local private utility methods + void RemoteActionMapping::setApiVersionNumber(unsigned int apiVersionNumber) + { + LOGINFO("setting version: %d", (int)apiVersionNumber); + m_apiVersionNumber = apiVersionNumber; + } + + bool RemoteActionMapping::setKeyGroups(KeyGroupSrcInfo& srcInfo, const KeyPresenceFlags& keyPresence) + { + bool bOK = true; + + // Input Select Group (straight-forward, TV has precedence) + if (keyPresence.tv.input) + { + srcInfo.groupInputSelect = KEY_GROUP_SRC_TV; + } + else if (keyPresence.avr.input) + { + srcInfo.groupInputSelect = KEY_GROUP_SRC_AVR; + } + else + { + LOGWARN("NO input control possible!"); + } + + // Volume/Mute Group (resolve VOL UP/DN and MUTE together - AVR has precedence) + if (keyPresence.avr.vol_up && keyPresence.avr.vol_dn) + { + srcInfo.groupVolume = KEY_GROUP_SRC_AVR; + if (keyPresence.avr.mute) + { + srcInfo.groupMute = KEY_GROUP_SRC_AVR; + } + else + { + LOGWARN("Choosing AVR volume control, but no Mute possible!"); + } + } + else if (keyPresence.tv.vol_up && keyPresence.tv.vol_dn) + { + srcInfo.groupVolume = KEY_GROUP_SRC_TV; + if (keyPresence.tv.mute) + { + srcInfo.groupMute = KEY_GROUP_SRC_TV; + } + else + { + LOGWARN("Choosing TV volume control, but no Mute possible!"); + } + } + else + { + // NO volume control available, via TV or AVR! Check for mute-only case. + LOGWARN("NO volume control possible!"); + if (keyPresence.avr.mute) + { + srcInfo.groupMute = KEY_GROUP_SRC_AVR; + } + else if (keyPresence.tv.mute) + { + srcInfo.groupMute = KEY_GROUP_SRC_TV; + } + else + { + // NO mute control possible! + LOGWARN("NO mute control possible!"); + } + } + + // Check volume group coherence + if ((srcInfo.groupVolume == KEY_GROUP_SRC_CLEAR) && + (keyPresence.tv.vol_up || keyPresence.tv.vol_dn || keyPresence.avr.vol_up || keyPresence.avr.vol_dn)) + { + LOGERR("Incoherent volume group!!"); + bOK = false; + } + + // Power Groups (resolve toggle and discrete together - TV has toggle precedence, AVR has discrete precedence) + bool tvdiscrete = keyPresence.tv.pwr_on && keyPresence.tv.pwr_off; + bool avrdiscrete = keyPresence.avr.pwr_on && keyPresence.avr.pwr_off; + bool notvpwr = !keyPresence.tv.pwr_toggle && !tvdiscrete; + bool noavrpwr = !keyPresence.avr.pwr_toggle && !avrdiscrete; + // Worry about TV+AVR cases first. For the XR11's sake, put a priority on the TV using the toggle slot. + if (keyPresence.tv.pwr_toggle && keyPresence.avr.pwr_toggle) + { + // Give the TV the toggle power slot, and populate both discrete slots with AVR toggle power codes. + srcInfo.groupTogglePower = KEY_GROUP_SRC_TV; + srcInfo.groupDiscretePower = KEY_GROUP_SRC_AVR_PWR_CROSS; + } + else if (keyPresence.tv.pwr_toggle && avrdiscrete) + { + // Again, give the TV the toggle power slot, but if there is no AVR toggle power, use AVR discrete. + srcInfo.groupTogglePower = KEY_GROUP_SRC_TV; + srcInfo.groupDiscretePower = KEY_GROUP_SRC_AVR; + } + else if (keyPresence.avr.pwr_toggle && tvdiscrete) + { + // At this point, in TV+AVR cases, we can't give the TV the toggle slot. + // Our only choice, so we have power control for both, is to give the toggle slot to the AVR. + // This will make the XR11 "TV Power" button affect the AVR, but we can't avoid that. + // We also can't avoid using TV discrete codes, which have proved unreliable in some cases. + srcInfo.groupTogglePower = KEY_GROUP_SRC_AVR; + srcInfo.groupDiscretePower = KEY_GROUP_SRC_TV; + } + else if (tvdiscrete && avrdiscrete) + { + // At this point, and inside this case, implies that there are NO power toggle codes, for TV or AVR. + // Sadly, we MUST abandon either TV or AVR power control, at least for 7-slot-IRRFDB controllers. + // We choose to lose AVR power control, in order to keep TV power control. + LOGWARN("Unresolvable conflict with discrete power - choosing power control over TV ONLY!"); + srcInfo.groupDiscretePower = KEY_GROUP_SRC_TV; + } + else if (noavrpwr) + { + // Handle TV with no AVR (at least for power control purposes) + if (keyPresence.tv.pwr_toggle) + { + srcInfo.groupTogglePower = KEY_GROUP_SRC_TV; + } + if (tvdiscrete) + { + srcInfo.groupDiscretePower = KEY_GROUP_SRC_TV; + } + else if (keyPresence.tv.pwr_toggle) + { + // Need to copy toggle power IR data into the discrete slots (for XR11 - two power buttons) + srcInfo.groupDiscretePower = KEY_GROUP_SRC_TV_PWR_CROSS; + } + } + else if (notvpwr) + { + // Handle AVR with no TV (at least for power control purposes) + if (keyPresence.avr.pwr_toggle) + { + srcInfo.groupTogglePower = KEY_GROUP_SRC_AVR; + } + if (avrdiscrete) + { + srcInfo.groupDiscretePower = KEY_GROUP_SRC_AVR; + } + else if (keyPresence.avr.pwr_toggle) + { + // Need to copy toggle power IR data into the discrete slots (for XR11 - two power buttons) + srcInfo.groupDiscretePower = KEY_GROUP_SRC_AVR_PWR_CROSS; + } + } + else + { + // Logically, if we get here, it MUST BE that there are NO TV or AVR power controls available. + if (notvpwr && noavrpwr) + { + LOGWARN("NO TV or AVR power control IR codes have been supplied!"); + } + else + { + LOGERR("LOGIC ERROR - power group decision failure - TV power: %s, AVR power: %s!!!", + (notvpwr ? "FALSE" : "TRUE"), (noavrpwr ? "FALSE" : "TRUE")); + bOK = false; + } + } + + // Check discrete power coherence + if (!tvdiscrete && (keyPresence.tv.pwr_on || keyPresence.tv.pwr_off)) + { + LOGERR("Incoherent TV discrete power!!"); + bOK = false; + } + if (!avrdiscrete && (keyPresence.avr.pwr_on || keyPresence.avr.pwr_off)) + { + LOGERR("Incoherent AVR discrete power!!"); + bOK = false; + } + + // Log the choices that were made. + LOGWARN(" groupDiscretePower: %d", srcInfo.groupDiscretePower); + LOGWARN(" groupTogglePower: %d", srcInfo.groupTogglePower); + LOGWARN(" groupVolume: %d", srcInfo.groupVolume); + LOGWARN(" groupMute: %d", srcInfo.groupMute); + LOGWARN(" groupInputSelect: %d", srcInfo.groupInputSelect); + + return bOK; + } + + bool RemoteActionMapping::checkClearList(RFKeyFlags& rfKeyFlags, int* keyNames, int* numNames) + { + bool bOK = true; + + // Check the volume group, but don't enforce mute as part of the group, for clearing. + if (rfKeyFlags.vol_up || rfKeyFlags.vol_dn) + { + if (!rfKeyFlags.vol_up) + { + LOGWARN("Attempt to clear volume down ONLY! - fixing..."); + if (*numNames < supported_ked_keynames_size) + { + keyNames[*numNames] = KED_VOLUMEUP; + (*numNames)++; + } + else + { + LOGERR("LOGIC ERROR - no room for KED_VOLUMEUP in list!!"); + bOK = false; + } + } + if (!rfKeyFlags.vol_dn) + { + LOGWARN("Attempt to clear volume up ONLY! - fixing..."); + if (*numNames < supported_ked_keynames_size) + { + keyNames[*numNames] = KED_VOLUMEDOWN; + (*numNames)++; + } + else + { + LOGERR("LOGIC ERROR - no room for KED_VOLUMEDOWN in list!!"); + bOK = false; + } + } + } + + // Check the discrete power group + if (rfKeyFlags.pwr_on || rfKeyFlags.pwr_off) + { + if (!rfKeyFlags.pwr_on) + { + LOGWARN("Attempt to clear discrete power off ONLY! - fixing..."); + if (*numNames < supported_ked_keynames_size) + { + keyNames[*numNames] = KED_DISCRETE_POWER_ON; + (*numNames)++; + } + else + { + LOGERR("LOGIC ERROR - no room for KED_DISCRETE_POWER_ON in list!!"); + bOK = false; + } + } + if (!rfKeyFlags.pwr_off) + { + LOGWARN("Attempt to clear discrete power on ONLY! - fixing..."); + if (*numNames < supported_ked_keynames_size) + { + keyNames[*numNames] = KED_DISCRETE_POWER_STANDBY; + (*numNames)++; + } + else + { + LOGERR("LOGIC ERROR - no room for KED_DISCRETE_POWER_STANDBY in list!!"); + bOK = false; + } + } + } + + return bOK; + } + + bool RemoteActionMapping::checkIRRFDBReadProgress(int_vector_t& rfKeyCodes) + { + bool tvOK = false; + bool avrOK = false; + + rfKeyCodes.clear(); + + // Generate the rfKeyCodes read list. + for (int i = 0; i < supported_irrfdb_slots_size; i++) + { + int rfKey = supported_irrfdb_slots[i]; + if (m_readProgress.getSlotRead(rfKey)) + { + rfKeyCodes.push_back(rfKey); + } + } + + // Decide whether or not all the appropriate slots have been read. + // Start with the four non-power legacy slots. We require that + // the controller reads all four of those. + if ((!m_readProgress.slot_INPUT_SELECT) || + (!m_readProgress.slot_VOL_PLUS) || + (!m_readProgress.slot_VOL_MINUS) || + (!m_readProgress.slot_MUTE)) + { + return false; + } + // Next, check the 3 legacy power slots. Some controllers will + // read all three, but some controllers won't read these at all. + if ((m_readProgress.slot_PWR_TOGGLE) && + (m_readProgress.slot_PWR_OFF) && + (m_readProgress.slot_PWR_ON)) + { + return true; + } + // Finally, check the 6 separate, dedicated TV and AVR power slots. + // We will require controllers that DON'T read the legacy power slots, + // above, to read both the 3 TV and the 3 AVR dedicated power slots. + if ((m_readProgress.slot_TV_PWR_TOGGLE) && + (m_readProgress.slot_TV_PWR_OFF) && + (m_readProgress.slot_TV_PWR_ON)) + { + tvOK = true; + } + if ((m_readProgress.slot_AVR_PWR_TOGGLE) && + (m_readProgress.slot_AVR_PWR_OFF) && + (m_readProgress.slot_AVR_PWR_ON)) + { + avrOK = true; + } + + return (tvOK && avrOK); + } + + void RemoteActionMapping::handleIRRFDBKeyRead(int deviceID, int rfKey) + { + int_vector_t rfKeyCodes; + bool done = false; + + LOGWARN("target remoteId(%d) has read IRRFDB data for rfKey: 0x%02X.", deviceID, rfKey); + // Mark this rfkey as read, in the controller read/load progress state. + m_readProgress.setSlotRead(rfKey, true); + + // Check to see if all the appropriate slots have been read yet. + done = checkIRRFDBReadProgress(rfKeyCodes); + + // Send the load event if the remote has read all the slots we expected + if (done) + { + stopRIBLoadTimer(); + LOGWARN("Sending onIRCodeLoad event - numKeys is %d.", rfKeyCodes.size()); + m_irdbLoadState = IRDB_LOAD_STATE_NONE; + onIRCodeLoad(deviceID, rfKeyCodes, IRCODE_LOAD_STATUS_OK); + m_lastSetRemoteID = -1; + m_lastSetHas5DCPresent = false; + m_lastSetSupports5DC = false; + m_ramsOperatingMode = RAMS_OP_MODE_NONE; + m_readProgress.clear(); + } + } + + void RemoteActionMapping::handleFiveDigitCodeAccess(int deviceID) + { + unsigned tvLoadStatus = 0; + unsigned avrLoadStatus = 0; + int tvStatus = 0; + int avrStatus = 0; + + // Send the load event if the remote has updated the Controller IRDB Status + if (m_irdbLoadState == IRDB_LOAD_STATE_CTRLR_WRITTEN) + { + stopRIBLoadTimer(); + // Get the Controller IRDB Status TV and AVR Load Status values. + if (!m_helper.getControllerLoadStatus(deviceID, tvLoadStatus, avrLoadStatus)) + { + LOGERR("helper getControllerLoadStatus() FAILED!"); + } + else + { + // Convert the RIB TV LoadStatus to the XRE TV LoadStatus + tvStatus = (int)(tvLoadStatus >> 4); + if (tvStatus > 6) + { + LOGERR("ERROR - Got invalid status %d from tvLoadStatus!?!", tvStatus); + } + // Convert the RIB AVR LoadStatus to the XRE AVR LoadStatus + avrStatus = (int)(avrLoadStatus >> 4); + if (avrStatus > 6) + { + LOGERR("ERROR - Got invalid status %d from avrLoadStatus!?!", avrStatus); + } + } + + LOGWARN("Sending onFiveDigitCodeLoad event - tvResult: 0x%02X, avrResult: 0x%02X.", + tvLoadStatus, avrLoadStatus); + m_irdbLoadState = IRDB_LOAD_STATE_NONE; + onFiveDigitCodeLoad(deviceID, tvStatus, avrStatus); + m_lastSetRemoteID = -1; + m_lastSetHas5DCPresent = false; + m_lastSetSupports5DC = false; + m_fiveDigitCodeMode = FIVE_DIGIT_CODE_MODE_NONE; + m_ramsOperatingMode = RAMS_OP_MODE_NONE; + } + else + { + LOGWARN("unexpected call with remoteId: %d, _irdbLoadState: %d!", deviceID, m_irdbLoadState); + } + } + + void RemoteActionMapping::handleRIBLoadTimeout() + { + std::lock_guard guard(m_stateMutex); + + if (m_ramsOperatingMode == RAMS_OP_MODE_IRRF_DATABASE) + { + // Handle this IRRF Database loading timeout + if ((m_irdbLoadState == IRDB_LOAD_STATE_PROGRESS) || + (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ)) + { + int_vector_t rfKeyCodes; + int status = IRCODE_LOAD_STATUS_TIMEOUT_INCOMPLETE; + + if (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ) + { + // Somehow, NONE of the IRDB data was read by the target controller. + // Clear the IRDB Download flag (back to default), to restore normal OK key operation. + m_helper.setIRDBDownloadFlag(m_lastSetRemoteID, false); + status = IRCODE_LOAD_STATUS_TIMEOUT_INCOMPLETE; + } + else + { + // Check to see if all the appropriate slots have been read. + if (checkIRRFDBReadProgress(rfKeyCodes)) + { + status = IRCODE_LOAD_STATUS_TIMEOUT_COMPLETE; + } + else + { + status = IRCODE_LOAD_STATUS_TIMEOUT_INCOMPLETE; + } + } + if (m_lastSetHas5DCPresent && (status == IRCODE_LOAD_STATUS_TIMEOUT_INCOMPLETE)) + { + // The controller didn't load the IRRF Database entries because + // it already had a 5-Digit Code set internally. + // Note that _lastSetHas5DCPresent can only be true for XRE API 3 or greater only! + status = IRCODE_LOAD_STATUS_REFUSED; + } + + LOGWARN("TIMEOUT: IRRF Database LoadState(%d) - sending onIRCodeLoad event - numKeys: %d, status: %d.", + m_irdbLoadState, rfKeyCodes.size(), status); + + m_irdbLoadState = IRDB_LOAD_STATE_NONE; + onIRCodeLoad(m_lastSetRemoteID, rfKeyCodes, status); + m_lastSetRemoteID = -1; + m_lastSetHas5DCPresent = false; + m_lastSetSupports5DC = false; + m_ramsOperatingMode = RAMS_OP_MODE_NONE; + m_readProgress.clear(); + } + else + { + LOGWARN("IRRF Database TIMEOUT without load-in-progress!"); + } + } + else if (m_ramsOperatingMode == RAMS_OP_MODE_FIVE_DIGIT_CODE) + { + // Handle this 5-Digit Code loading timeout + if (m_irdbLoadState >= IRDB_LOAD_STATE_FLAG_READ) + { + int tvStatus = 16; // Timeout error codes + int avrStatus = 16; + + // Only set the timeout error indication into the proper result(s) for what was being attempted. + if (m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_TV_SET) + { + avrStatus = 0; + } + else if (m_fiveDigitCodeMode == FIVE_DIGIT_CODE_MODE_AVR_SET) + { + tvStatus = 0; + } + + if (m_irdbLoadState == IRDB_LOAD_STATE_FLAG_READ) + { + // Somehow, the target remote never did anything, despite having read the IRRF Status Flags. + // Clear the 5-Digit Code flag(s) (back to default), to restore normal OK key operation. + m_helper.setFiveDigitCodeFlags(m_lastSetRemoteID, FIVE_DIGIT_CODE_MODE_NONE); + } + + LOGWARN("5-Digit Code TIMEOUT: _fiveDigitCodeMode: %d, _irdbLoadState: %d, sending onFiveDigitCodeLoad event - tvStatus: %d, avrStatus: %d.", + m_fiveDigitCodeMode, m_irdbLoadState, tvStatus, avrStatus); + + m_irdbLoadState = IRDB_LOAD_STATE_NONE; + onFiveDigitCodeLoad(m_lastSetRemoteID, tvStatus, avrStatus); + m_lastSetRemoteID = -1; + m_lastSetHas5DCPresent = false; + m_lastSetSupports5DC = false; + m_fiveDigitCodeMode = FIVE_DIGIT_CODE_MODE_NONE; + m_ramsOperatingMode = RAMS_OP_MODE_NONE; + } + else + { + LOGWARN("5-Digit Code TIMEOUT without load-in-progress!"); + } + } else { + LOGWARN("Bogus m_ramsOperatingMode: %d!", m_ramsOperatingMode); + } + } + + void RemoteActionMapping::startRIBLoadTimer(int msec) + { + stopRIBLoadTimer(); + ribLoadTimer.Schedule(Core::Time::Now().Add(msec), m_ribLoadTimeoutImpl); + LOGINFO("RIB Load Timer started - time: %dms.", msec); + } + + void RemoteActionMapping::stopRIBLoadTimer() + { + ribLoadTimer.Revoke(m_ribLoadTimeoutImpl); + LOGINFO("RIB Load Timer stopped."); + } + //End local private utility methods + + // Friend class RibLoadTimeoutImpl public Timed method implementation. + // Core::TimerType callback name is fixed, so this method MUST be called "Timed"! + uint64_t RibLoadTimeoutImpl::Timed(const uint64_t scheduledTime) + { + uint64_t result = 0; + m_ram->handleRIBLoadTimeout(); + return(result); + } + + } // namespace Plugin + +} // namespace WPEFramework diff --git a/RemoteActionMapping/RemoteActionMapping.h b/RemoteActionMapping/RemoteActionMapping.h new file mode 100644 index 0000000000..dffd5a9874 --- /dev/null +++ b/RemoteActionMapping/RemoteActionMapping.h @@ -0,0 +1,203 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include "Module.h" +#include "utils.h" +#include "AbstractPlugin.h" + +#include "RamHelper.h" + +#include +#include + +#define IARM_REMOTEACTIONMAPPING_PLUGIN_NAME "Remote_Action_Mapping" + +#define RAM_TIMER_THREAD_STACK_SIZE (64 * 1024) +#define RAM_TIMER_THREAD_NAME "RAMRIBLoadTimer" + +// Substitute for the old AbstractService Status enumeration +typedef enum { + STATUS_OK = 0, + STATUS_FAILURE, + STATUS_INVALID_ARGUMENT, + STATUS_INVALID_STATE, + STATUS_METHOD_NOT_FOUND +} StatusCode; + +// To track the different states of the controller-event-driven loading/clearing sequence (for IRRF Database or 5-Digit Codes) +typedef enum { + IRDB_LOAD_STATE_NONE, // Idle. + IRDB_LOAD_STATE_WRITTEN, // IRRF Database entries (IRRF_DATABASE mode), or Target IRDB Status TV/AVR 5-Digit Codes (FIVE_DIGIT_CODE mode), are set. + IRDB_LOAD_STATE_FLAG_READ, // IRRF Status Flags (both modes) have been read by the controller. + IRDB_LOAD_STATE_PROGRESS, // IRRF Database entries are being read by the controller. IRRF_DATABASE mode only. + IRDB_LOAD_STATE_TARGET_READ, // Target IRDB Status TV/AVR 5-Digit Codes have been read by the controller. FIVE_DIGIT_CODE mode only. + IRDB_LOAD_STATE_CTRLR_WRITTEN // Controller IRDB Status has been written (updated) by the controller. FIVE_DIGIT_CODE mode only. +} IRDBLoadState; + +typedef enum { + RAMS_OP_MODE_NONE, + RAMS_OP_MODE_IRRF_DATABASE, + RAMS_OP_MODE_FIVE_DIGIT_CODE +} RAMSOperatingMode; + +// Timeout values for the remote reading the RIB IRRF Database entries, in milliseconds +// NOTE that there IS NO TIMEOUT for how long it takes the customer to press the OK key!! +#define TIMEOUT_REMOTE_IRRFDB_READ_IRCODE_START 4000 // Max delay between OK keypress read and 1st key IRCode read +#define TIMEOUT_REMOTE_IRRFDB_READ_IRCODE_NEXT 900 // Max delay between key IRCode reads, once they start +// Timeout values for the remote reading the RIB 5-Digit Codes, and subsequent writing controller status +#define TIMEOUT_REMOTE_FIVEDIGITCODE_READ_TARGET 500 // Max delay between OK keypress read and Target IRDB Status read +#define TIMEOUT_REMOTE_FIVEDIGITCODE_WRITE_CONTROLLER 1600 // Max delay between Target IRDB Status read and Controller IRDB Status write + + + +namespace WPEFramework { + + namespace Plugin { + + class RemoteActionMapping; // Forward declaration + + // Load timer implementation, for timeout on controller loading from the RIB + class RibLoadTimeoutImpl + { + private: + RibLoadTimeoutImpl() = delete; + RibLoadTimeoutImpl& operator=(const RibLoadTimeoutImpl& RHS) = delete; + + public: + RibLoadTimeoutImpl(RemoteActionMapping* ram): m_ram(ram){} + RibLoadTimeoutImpl(const RibLoadTimeoutImpl& copy): m_ram(copy.m_ram){} + ~RibLoadTimeoutImpl() {} + + inline bool operator==(const RibLoadTimeoutImpl& RHS) const + { + return(m_ram == RHS.m_ram); + } + + public: + uint64_t Timed(const uint64_t scheduledTime); // The method MUST be called "Timed"! + + private: + RemoteActionMapping* m_ram; + }; + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other method to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + // Note that most of the above is now inherited from the AbstractPlugin class. + class RemoteActionMapping : public AbstractPlugin { + private: + typedef Core::JSON::String JString; + typedef Core::JSON::ArrayType JStringArray; + typedef Core::JSON::ArrayType JObjectArray; + typedef Core::JSON::Boolean JBool; + + // We do not allow this plugin to be copied !! + RemoteActionMapping(const RemoteActionMapping&) = delete; + RemoteActionMapping& operator=(const RemoteActionMapping&) = delete; + + //Begin methods + uint32_t getApiVersionNumber(const JsonObject& parameters, JsonObject& response); + + uint32_t getLastUsedDeviceIDWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getKeymapWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t clearKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getFullKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t getSingleKeyActionMappingWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t cancelCodeDownloadWrapper(const JsonObject& parameters, JsonObject& response); + uint32_t setFiveDigitCodeWrapper(const JsonObject& parameters, JsonObject& response); + //End methods + + //Begin events + void onIRCodeLoad(int deviceID, int_vector_t& rfKeyCodes, int status); + void onFiveDigitCodeLoad(int deviceID, int tvStatus, int avrStatus); + //End events + + public: + RemoteActionMapping(); + virtual ~RemoteActionMapping(); + //IPlugin methods + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + private: + void InitializeIARM(); + void DeinitializeIARM(); + // Handlers for IARM events + static void ramEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + void iarmEventHandler(const char *owner, IARM_EventId_t eventId, void *data, size_t len); + + // Underlying private implementations for public wrapper methods + int getLastUsedDeviceID(std::string& remoteType, bool& pbFiveDigitCodeSet, bool& pbFiveDigitCodeSupported); + JsonArray getKeymap(int deviceID, int keymapType); + bool setKeyActionMapping(int deviceID, int keymapType, std::map& localActionMaps, const KeyGroupSrcInfo& srcInfo ); + bool clearKeyActionMapping(int deviceID, int keymapType, int* keyNames, int numNames); + JObjectArray getFullKeyActionMapping(int deviceID, int keymapType); + JsonObject getSingleKeyActionMapping(int deviceID, int keymapType, int keyName); + bool cancelCodeDownload(int deviceID); + bool setFiveDigitCode(int deviceID, int tvFiveDigitCode, int avrFiveDigitCode); + + // Local utility methods + void setApiVersionNumber(uint32_t apiVersionNumber); + + bool setKeyGroups(KeyGroupSrcInfo& srcInfo, const KeyPresenceFlags& keyPresence); + bool checkClearList(RFKeyFlags& rfKeyFlags, int* keyNames, int* numNames); + + bool checkIRRFDBReadProgress(int_vector_t& rfKeyCodes); + void handleIRRFDBKeyRead(int deviceID, int rfKey); + void handleFiveDigitCodeAccess(int deviceID); + void handleRIBLoadTimeout(); + + void startRIBLoadTimer(int msec); + void stopRIBLoadTimer(); + + public: + static RemoteActionMapping* _instance; + private: + uint32_t m_apiVersionNumber; + + std::mutex m_stateMutex; + + RemoteActionMappingHelper m_helper; + friend class RemoteActionMappingHelper; + + RibLoadTimeoutImpl m_ribLoadTimeoutImpl; + friend class RibLoadTimeoutImpl; + + // State machine + static IRDBLoadState m_irdbLoadState; + static IRRFDBCtrlrLoadProgress m_readProgress; + static int m_lastSetRemoteID; + static int m_ramsOperatingMode; + static int m_fiveDigitCodeMode; + static bool m_lastSetHas5DCPresent; + static bool m_lastSetSupports5DC; + }; + } // namespace Plugin +} // namespace WPEFramework diff --git a/RemoteActionMapping/cmake/FindCTRLM.cmake b/RemoteActionMapping/cmake/FindCTRLM.cmake new file mode 100644 index 0000000000..2071c7c162 --- /dev/null +++ b/RemoteActionMapping/cmake/FindCTRLM.cmake @@ -0,0 +1,36 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +# - Try to find ControlManager +# Once done this will define +# CTRLM_FOUND - System has ControlManager +# CTRLM_INCLUDE_DIRS - The ControlManager include directories +# + +find_package(PkgConfig) + +find_path(CTRLM_INCLUDE_DIRS NAMES ctrlm_ipc.h) + +set(CTRLM_INCLUDE_DIRS ${CTRLM_INCLUDE_DIRS}) +set(CTRLM_INCLUDE_DIRS ${CTRLM_INCLUDE_DIRS} CACHE PATH "Path to ControlManager include") + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(CTRLM DEFAULT_MSG CTRLM_INCLUDE_DIRS) + +mark_as_advanced( + CTRLM_FOUND + CTRLM_INCLUDE_DIRS) diff --git a/RemoteActionMapping/cmake/FindIARMBus.cmake b/RemoteActionMapping/cmake/FindIARMBus.cmake new file mode 100644 index 0000000000..212699aac4 --- /dev/null +++ b/RemoteActionMapping/cmake/FindIARMBus.cmake @@ -0,0 +1,45 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +# - Try to find IARMBus +# Once done this will define +# IARMBUS_FOUND - System has IARMBus +# IARMBUS_INCLUDE_DIRS - The IARMBus include directories +# IARMBUS_LIBRARIES - The libraries needed to use IARMBus +# IARMBUS_FLAGS - The flags needed to use IARMBus +# + +find_package(PkgConfig) + +find_library(IARMBUS_LIBRARIES NAMES IARMBus) +find_path(IARMBUS_INCLUDE_DIRS NAMES libIARM.h PATH_SUFFIXES rdk/iarmbus) +find_path(IRMGR_INCLUDE_DIRS NAMES irMgr.h comcastIrKeyCodes.h PATH_SUFFIXES rdk/iarmmgrs/ir rdk/iarmmgrs-hal) + +set(IARMBUS_LIBRARIES ${IARMBUS_LIBRARIES} CACHE PATH "Path to IARMBus library") +set(IARMBUS_INCLUDE_DIRS ${IARMBUS_INCLUDE_DIRS} ${IRMGR_INCLUDE_DIRS}) +set(IARMBUS_INCLUDE_DIRS ${IARMBUS_INCLUDE_DIRS} ${IRMGR_INCLUDE_DIRS} CACHE PATH "Paths to IARMBus include") + + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(IARMBUS DEFAULT_MSG IARMBUS_INCLUDE_DIRS IARMBUS_LIBRARIES) + +mark_as_advanced( + IARMBUS_FOUND + IARMBUS_INCLUDE_DIRS + IARMBUS_LIBRARIES + IARMBUS_LIBRARY_DIRS + IARMBUS_FLAGS) diff --git a/RemoteActionMapping/test/CMakeLists.txt b/RemoteActionMapping/test/CMakeLists.txt new file mode 100644 index 0000000000..164641d0d7 --- /dev/null +++ b/RemoteActionMapping/test/CMakeLists.txt @@ -0,0 +1,32 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME ramTestClient) +find_package(${NAMESPACE}Protocols REQUIRED) + +add_executable(${PLUGIN_NAME} ramTestClient.cpp) + +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + ) + +target_link_libraries(${PLUGIN_NAME} + PRIVATE + ${NAMESPACE}Protocols::${NAMESPACE}Protocols) + +install(TARGETS ${PLUGIN_NAME} DESTINATION bin) diff --git a/RemoteActionMapping/test/Module.h b/RemoteActionMapping/test/Module.h new file mode 100644 index 0000000000..a20c5d31ba --- /dev/null +++ b/RemoteActionMapping/test/Module.h @@ -0,0 +1,27 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME ramTestClient +#endif + +#include +#include diff --git a/RemoteActionMapping/test/ramTestClient.cpp b/RemoteActionMapping/test/ramTestClient.cpp new file mode 100644 index 0000000000..d395de8fc3 --- /dev/null +++ b/RemoteActionMapping/test/ramTestClient.cpp @@ -0,0 +1,809 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2019 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +/** + * @file ramTestClient.cpp + * @brief Thunder Plugin based Implementation of CPP Test Client for RemoteActionMapping service API's. + * @reference RDK-25853. + */ + +#include +#include +#include + +#include "Module.h" + +#include +#include +#include + +#include + +#define SYSSRV_CALLSIGN "org.rdk.RemoteActionMapping" +#define SERVER_DETAILS "127.0.0.1:9998" + +using namespace std; +using namespace WPEFramework; + +typedef std::vector int_vector_t; + +#define MAX_MSO_RF_CODES (7) +// Global data used to for arguments to pass to the plugin methods +int deviceID = 1; +int keymapType = 0; // 0 == IRDB, 1 == All Keys + +int currentTVFiveDigitCode = 0; +int currentAVRFiveDigitCode = 0; + +const int_vector_t* currentTVIRData = NULL; +const int_vector_t* currentAVRIRData = NULL; + +const std::vector tvDevices = { "No TV", "Samsung", "Magnavox", "LG", "Samsung w/NoToggle", "Samsung w/NoDiscrete" }; +const std::vector avrDevices = { "No AVR", "Sony", "Onkyo", "Yamaha", "Sony w/NoToggle" , "Sony w/NoDiscrete" }; + +int currentTVidx = 0; +string currentTV = tvDevices[0]; +int currentAVRidx = 0; +string currentAVR = avrDevices[0]; + +//POWER_TOGGLE, POWER_OFF, POWER_ON, VOL+, VOL-, MUTE, INPUT/SELECT +const int RFKeyCodes[MAX_MSO_RF_CODES] = {0x6B, 0x6C, 0x6D, 0x41, 0x42, 0x43, 0x34}; +//KED_POWER, KED_DSCRT_PWR_STANDBY, KED_DSCRT_PWR_ON, KED_VOLUMEUP, KED_VOLUMEDOWN, KED_MUTE, KED_INPUTKEY +const int KEDKeyNames[MAX_MSO_RF_CODES] = {0x80, 0x51, 0x50, 0x8A, 0x8B, 0x8C, 0xD0}; + +// Samsung TVs (LN46C610N1F, UN22F5000AF, UN46B8000XF, P2370HD +const int_vector_t samsungTVIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x22, 0x20}, + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x33, 0x22, 0x33, 0x22, 0x22, 0x33, 0x22, 0x30}, + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x33, 0x22, 0x33, 0x23, 0x22, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x23, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x22, 0x20} +}; + +// Sony AVRs (STR-DH500, STR-DH740) +const int_vector_t sonyAVRIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x18, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xEC, 0x13, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x82, 0x14, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x18, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x00} +}; + +// Magnavox TV (24ME403V/F7) +const int_vector_t magnavoxTVIRData[MAX_MSO_RF_CODES] = +{ + {0x0E, 0x11, 0x03, 0x00, 0x1D, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xAF, 0x57, 0x00, 0x00, 0xDE, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x12, 0x12, 0x21, 0x21, 0x00, 0x04, 0x02, 0x21, 0x12, 0x21}, + {0x00}, + {0x00}, + {0x0E, 0x11, 0x03, 0x00, 0x1D, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xAF, 0x57, 0x00, 0x00, 0xDE, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x12, 0x21, 0x21, 0x21, 0x21, 0x00, 0x04, 0x02, 0x21, 0x12, 0x21}, + {0x0E, 0x11, 0x03, 0x00, 0x1D, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xAF, 0x57, 0x00, 0x00, 0xDE, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x12, 0x21, 0x21, 0x21, 0x12, 0x00, 0x04, 0x02, 0x21, 0x12, 0x21}, + {0x0E, 0x11, 0x03, 0x00, 0x1D, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xAF, 0x57, 0x00, 0x00, 0xDE, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x12, 0x12, 0x21, 0x12, 0x00, 0x04, 0x02, 0x21, 0x12, 0x21}, + {0x0E, 0x11, 0x03, 0x00, 0x1D, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xAF, 0x57, 0x00, 0x00, 0xDE, 0x00, 0xDE, 0x00, 0x00, 0x00, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x21, 0x21, 0x12, 0x12, 0x12, 0x21, 0x21, 0x21, 0x00, 0x04, 0x02, 0x21, 0x12, 0x21} +}; + +// Onkyo AVR (HT-R380) +const int_vector_t onkyoAVRIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x11, 0x06, 0x26, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x54, 0x54, 0x45, 0x54, 0x45, 0x44, 0x54, 0x44, 0x55, 0x54, 0x55, 0x55, 0x44, 0x45, 0x41, 0x30, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x26, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xE3, 0x25, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x54, 0x54, 0x44, 0x54, 0x45, 0x44, 0x55, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x41, 0x30, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x26, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x54, 0x54, 0x45, 0x54, 0x45, 0x44, 0x54, 0x44, 0x55, 0x54, 0x55, 0x55, 0x44, 0x45, 0x41, 0x30, 0x30, 0x30}, + {0x04, 0x11, 0x04, 0x44, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xE3, 0x25, 0xCA, 0x08, 0x65, 0x04, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x33, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x22, 0x20, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x33, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x22, 0x20, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x33, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x44, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xE3, 0x25, 0xCA, 0x08, 0x65, 0x04, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x32, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x22, 0x20, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x32, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x22, 0x20, 0x13, 0x23, 0x32, 0x32, 0x22, 0x32, 0x23, 0x22, 0x32, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x22, 0x20}, + {0x04, 0x11, 0x06, 0x26, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xE3, 0x25, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x54, 0x54, 0x44, 0x54, 0x45, 0x44, 0x54, 0x54, 0x55, 0x55, 0x55, 0x45, 0x44, 0x44, 0x41, 0x30, 0x30, 0x30}, + {0x00} +}; + +// Yamaha AVR (RX-V373) +const int_vector_t yamahaAVRIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x44, 0x44, 0x44, 0x54, 0x55, 0x55, 0x55, 0x45, 0x45, 0x45, 0x45, 0x54, 0x54, 0x54, 0x54, 0x41, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x44, 0x44, 0x44, 0x54, 0x55, 0x55, 0x55, 0x45, 0x44, 0x44, 0x44, 0x54, 0x55, 0x55, 0x55, 0x41, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x44, 0x44, 0x44, 0x54, 0x55, 0x55, 0x55, 0x44, 0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x55, 0x41, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x44, 0x44, 0x54, 0x54, 0x55, 0x55, 0x45, 0x45, 0x44, 0x55, 0x54, 0x54, 0x55, 0x44, 0x41, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x44, 0x44, 0x54, 0x54, 0x55, 0x55, 0x44, 0x45, 0x44, 0x55, 0x55, 0x54, 0x55, 0x44, 0x41, 0x30, 0x30}, + {0x04, 0x11, 0x06, 0x24, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0xFC, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA2, 0x01, 0x90, 0x00, 0x89, 0x00, 0x25, 0x45, 0x44, 0x44, 0x54, 0x54, 0x55, 0x55, 0x45, 0x54, 0x44, 0x55, 0x54, 0x45, 0x55, 0x44, 0x41, 0x30, 0x30}, + {0x00} +}; + +// Samsung TVs (LN46C610N1F, UN22F5000AF, UN46B8000XF, P2370HD, but missing toggle power IR code +const int_vector_t samsungNoTogTVIRData[MAX_MSO_RF_CODES] = +{ + {0x00}, + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x33, 0x22, 0x33, 0x22, 0x22, 0x33, 0x22, 0x30}, + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x33, 0x22, 0x33, 0x23, 0x22, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x23, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x22, 0x20} +}; + +// Samsung TVs (LN46C610N1F, UN22F5000AF, UN46B8000XF, P2370HD, but missing discrete power on and off IR codes +const int_vector_t samsungNoDisTVIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x13, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x22, 0x20}, + {0x00}, + {0x00}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x23, 0x23, 0x33, 0x33, 0x32, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x22, 0x23, 0x33, 0x33, 0x33, 0x32, 0x22, 0x20}, + {0x04, 0x11, 0x04, 0x00, 0x22, 0x00, 0xD2, 0x00, 0x90, 0x00, 0x04, 0x2D, 0x65, 0x04, 0x65, 0x04, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x12, 0x22, 0x33, 0x33, 0x32, 0x22, 0x33, 0x33, 0x32, 0x33, 0x33, 0x33, 0x33, 0x22, 0x22, 0x22, 0x20} +}; + +// Sony AVRs (STR-DH500, STR-DH740) +const int_vector_t sonyNoTogAVRIRData[MAX_MSO_RF_CODES] = +{ + {0x00}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xEC, 0x13, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x12, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x82, 0x14, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30, 0x13, 0x22, 0x23, 0x23, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x18, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x00} +}; + +// Sony AVRs (STR-DH500, STR-DH740) +const int_vector_t sonyNoDisAVRIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x18, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x00}, + {0x00}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0x18, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x12, 0x23, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x04, 0x11, 0x04, 0x20, 0x10, 0x00, 0xC8, 0x00, 0x96, 0x00, 0xAE, 0x15, 0x58, 0x02, 0x96, 0x00, 0x2C, 0x01, 0x96, 0x00, 0x96, 0x00, 0x96, 0x00, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30, 0x13, 0x32, 0x32, 0x33, 0x33, 0x33, 0x22, 0x30}, + {0x00} +}; + +// LG TV (India model 22LD310) +const int_vector_t lgTVIRData[MAX_MSO_RF_CODES] = +{ + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x45, 0x55, 0x45, 0x55, 0x54, 0x44, 0x54, 0x44, 0x41, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x44, 0x54, 0x55, 0x54, 0x45, 0x45, 0x44, 0x45, 0x51, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x45, 0x54, 0x55, 0x54, 0x44, 0x45, 0x44, 0x45, 0x51, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x45, 0x45, 0x55, 0x55, 0x54, 0x54, 0x44, 0x44, 0x41, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x44, 0x45, 0x55, 0x55, 0x55, 0x54, 0x44, 0x44, 0x41, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x44, 0x55, 0x45, 0x55, 0x55, 0x44, 0x54, 0x44, 0x41, 0x30}, + {0x04, 0x11, 0x06, 0x22, 0x02, 0x00, 0xD2, 0x00, 0x90, 0x00, 0xEB, 0x5D, 0x90, 0x00, 0x6C, 0x26, 0xCA, 0x08, 0x65, 0x04, 0xCA, 0x08, 0x32, 0x02, 0x90, 0x00, 0xA6, 0x01, 0x90, 0x00, 0x8D, 0x00, 0x25, 0x54, 0x55, 0x55, 0x54, 0x45, 0x44, 0x44, 0x44, 0x45, 0x45, 0x55, 0x55, 0x54, 0x54, 0x44, 0x41, 0x30} +}; + + +/* This section can be used for API validation logic. */ +void showMenu() +{ + + std::cout<<"\n\nRemoteActionMapping API Methods: Current Parameter Settings:\n"; + std::cout<<"0.getQuirks() a.Cycle thru 'deviceID' values [1 thru 9]: " << deviceID << "\n"; + std::cout<<"1.getApiVersionNumber() b.Toggle 'keymapType' [IRDB Keys(0) | All Keys(1)]: " << keymapType << "\n"; + std::cout<<"2.getLastUsedDeviceID() c.Cycle thru available TV devices: " << currentTV << "\n"; + std::cout<<"3.getKeymap() d.Cycle thru available AVR devices: " << currentAVR << "\n"; + std::cout<<"4.setKeyActionMapping() e.Enter the 'tvFiveDigitCode' value: " << currentTVFiveDigitCode << "\n"; + std::cout<<"5.setFiveDigitCode() f.Enter the 'avrFiveDigitCode' value: " << currentAVRFiveDigitCode << "\n"; + std::cout<<"6.clearKeyActionMapping() (all keys cleared)\n"; + std::cout<<"7.getFullKeyActionMapping()\n"; + std::cout<<"8.getSingleKeyActionMapping() (MUTE key)\n"; + std::cout<<"9.cancelCodeDownload()\n"; + std::cout<<"\nEnter your choice: "; +} + +// Modified from normal, for display of key map arrays +void formatForDisplay(std::string& str) +{ + const int indent = 4; + int level = 0; + bool bInArray = false; + + for (size_t i = 0; i < str.size(); i++) + { + if (!bInArray && (str[i] == ',')) + { + str.insert((i+1), 1, '\n'); // insert after + if (level > 0) + { + str.insert((i+2), (level * indent), ' '); + } + } + else if (str[i] == '}') + { + level--; + if (level < 0) level = 0; + str.insert((i), 1, '\n'); // insert before + if (level > 0) + { + // after the newline, but before the curly brace + str.insert((i+1), (level * indent), ' '); + } + i += (level * indent) + 1; // put i back on the curly brace + } + else if (str[i] == '{') + { + bInArray = false; + level++; + str.insert((i+1), 1, '\n'); // insert after + if (level > 0) + { + str.insert((i+2), (level * indent), ' '); + } + } + else if (str[i] == '[') + { + bInArray = true; + } + else if (str[i] == ']') + { + bInArray = false; + } + } +} + +// Event parameters sent to JSONRPC::Client event handlers, have somehow +// acquired overall quotation marks, with internal quotes backslash-escaped. +// This function tries to detect and correct that, for console output. +// Should have no effect if the in/out string isn't inside double-quotes. +void removeJsonQuotes(std::string& str) +{ + if ((str.front() == '"') && (str.back() == '"')) + { + str.erase(0, 1); + str.pop_back(); + + for (size_t i = 0; i < str.size(); i++) + { + if ((i < (str.size() - 1)) && (str[i] == 0x5c) && (str[i+1] == '"')) + { + str.erase(i, 1); + } + } + } +} + +/*** + * @brief : compare two C string case insensitively + * @param1[in] : c string 1 + * @param2[in] : c string 2 + * @return : ; 0 if strings are same, some number if strings are different + */ +int strcicmp(char const *a, char const *b) +{ + int d = -1; + for (;; a++, b++) { + d = tolower((unsigned char)*a) - tolower((unsigned char)*b); + if (d != 0 || !*a) { + return d; + } + } + return -1; +} + +void setCurrentTVData() { + switch(currentTVidx){ + case 1: + { + currentTVIRData = samsungTVIRData; + break; + } + case 2: { + currentTVIRData = magnavoxTVIRData; + break; + } + case 3: + { + currentTVIRData = lgTVIRData; + break; + } + case 4: + { + currentTVIRData = samsungNoTogTVIRData; + break; + } + case 5: + { + currentTVIRData = samsungNoDisTVIRData; + break; + } + default: + { + currentTVIRData = NULL; + } + } +} + +void setCurrentAVRData() { + switch(currentAVRidx){ + case 1: { + currentAVRIRData = sonyAVRIRData; + break; + } + case 2: { + currentAVRIRData = onkyoAVRIRData; + break; + } + case 3: { + currentAVRIRData = yamahaAVRIRData; + break; + } + case 4: { + currentAVRIRData = sonyNoTogAVRIRData; + break; + } + case 5: { + currentAVRIRData = sonyNoDisAVRIRData; + break; + } + default: { + currentAVRIRData = NULL; + } + } +} + +// Make changes in parameters +void handleParams(string& cmd) +{ + switch(cmd[0]) + { + case 'a': + { + // device ID + if (deviceID < 9) + { + deviceID++; + } + else + { + deviceID = 1; + } + } + break; + + case 'b': + { + // keymapType + if (keymapType == 0) + { + keymapType = 1; + } + else + { + keymapType = 0; + } + } + break; + + case 'c': + { + // Cycle thru TV devices + if (currentTVidx < ((int)tvDevices.size() - 1)) { + currentTVidx++; + } else { + currentTVidx = 0; + } + currentTV = tvDevices[currentTVidx]; + setCurrentTVData(); + } + break; + + case 'd': + { + // Cycle thru AVR devices + if (currentAVRidx < ((int)avrDevices.size() - 1)) { + currentAVRidx++; + } else { + currentAVRidx = 0; + } + currentAVR = avrDevices[currentAVRidx]; + setCurrentAVRData(); + } + break; + + case 'e': + { + // Enter TV 5-digit-code + string digits; + int number = 0; + while (digits.empty()) + { + std::cout << "Enter the TV 5-digit-code (must be 5 numeric digits):"; + std::getline(std::cin, digits); + } + number = stoi(digits); + if (number <= 0) + { + std::cout << "Zeroing TV 5-digit-code!"; + currentTVFiveDigitCode = 0; + } + else if ((number < 9999) || (number > 99999)) + { + std::cout << "TV 5-digit-code " << digits << " out of range! No change!"; + } + else + { + currentTVFiveDigitCode = number; + } + } + break; + + case 'f': + { + // Enter AVR 5-digit-codes + string digits; + int number = 0; + while (digits.empty()) + { + std::cout << "Enter the AVR 5-digit-code (must be 5 numeric digits):"; + std::getline(std::cin, digits); + } + number = stoi(digits); + if (number <= 0) + { + std::cout << "Zeroing AVR 5-digit-code!"; + currentAVRFiveDigitCode = 0; + } + else if ((number < 9999) || (number > 99999)) + { + std::cout << "AVR 5-digit-code " << digits << " out of range! No change!"; + } + else + { + currentAVRFiveDigitCode = number; + } + } + break; + } +} + +/* This section is related to the event handler implementation for RemoteActionMapping Plugin Events. */ + +namespace Handlers { + /* Event Handlers */ + static void onIRCodeLoad(const Core::JSON::String& parameters) { + std::string message; + parameters.ToString(message); + removeJsonQuotes(message); + std::cout << "\n[RAMEvent] " << __FUNCTION__ << ": " << message << std::endl; + } + static void onFiveDigitCodeLoad(const Core::JSON::String& parameters) { + std::string message; + parameters.ToString(message); + removeJsonQuotes(message); + std::cout << "\n[RAMEvent] " << __FUNCTION__ << ": " << message << std::endl; + } +} + +int main(int argc, char** argv) +{ + JSONRPC::LinkType* remoteObject = NULL; + + int choice; + uint32_t ret; + string json; + string cmd; + + + Core::SystemInfo::SetEnvironment(_T("THUNDER_ACCESS"), (_T(SERVER_DETAILS))); + + if (NULL == remoteObject) { + remoteObject = new JSONRPC::LinkType(_T(SYSSRV_CALLSIGN), _T("")); + if (NULL == remoteObject) { + std::cout << "JSONRPC::Client initialization failed" << std::endl; + } else { + + /* Register handlers for Event reception. */ + std::cout << "\nSubscribing to event handlers\n" << std::endl; + if (remoteObject->Subscribe(1000, _T("onIRCodeLoad"), + &Handlers::onIRCodeLoad) == Core::ERROR_NONE) { + std::cout << "Subscribed to : onIRCodeLoad" << std::endl; + } else { + std::cout << "Failed to Subscribe notification handler : onIRCodeLoad" << std::endl; + } + if (remoteObject->Subscribe(1000, _T("onFiveDigitCodeLoad"), + &Handlers::onFiveDigitCodeLoad) == Core::ERROR_NONE) { + std::cout << "Subscribed to : onFiveDigitCodeLoad" << std::endl; + } else { + std::cout << "Failed to Subscribe notification handler : onFiveDigitCodeLoad" << std::endl; + } + + /* API Validation Logic. */ + while (true) { + json.clear(); + cmd.clear(); + while (cmd.empty()) + { + showMenu(); + std::getline(std::cin, cmd); + } + if ((cmd[0] >= '0') && (cmd[0] <= '9')) + { + choice = stoi(cmd); + } + else if ((cmd[0] >= 'a') && (cmd[0] <= 'f')) + { + handleParams(cmd); + continue; + } + else return 0; + + { + JsonObject result; // The Clear method can leave crud. A new instance off the stack works better. + + switch (choice) { + case 0: + { + JsonObject params; + string res; + ret = remoteObject->Invoke(1000, + _T("getQuirks"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getQuirks call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getQuirks call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("getApiVersionNumber"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getApiVersionNumber call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getApiVersionNumber call - failed!! \n"; + } + std::cout<<"result : "<Invoke(1000, + _T("getLastUsedDeviceID"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getLastUsedDeviceID call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getLastUsedDeviceID call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("getKeymap"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getKeymap call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getKeymap call - failed!\n"; + } + std::cout<<"result : "< 1)) + { + for (int j = 0; j < (int)currentTVIRData[i].size(); j++) + { + tvIRKeyCode.Add(JsonValue(currentTVIRData[i][j])); + } + } + keyMap["tvIRKeyCode"] = JsonValue(tvIRKeyCode); + + if ((currentAVRIRData != NULL) && (currentAVRIRData[i].size() > 1)) + { + for (int j = 0; j < (int)currentAVRIRData[i].size(); j++) + { + avrIRKeyCode.Add(JsonValue(currentAVRIRData[i][j])); + } + } + keyMap["avrIRKeyCode"] = JsonValue(avrIRKeyCode); + + array.Add(keyMap); + } + params["keyActionMapping"] = JsonValue(array); + + ret = remoteObject->Invoke(1000, + _T("setKeyActionMapping"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping setKeyActionMapping call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping setKeyActionMapping call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("setFiveDigitCode"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping setFiveDigitCode call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping setFiveDigitCode call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("clearKeyActionMapping"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"clearKeyActionMapping setValues: success\n"; + } else { + std::cout<<"clearKeyActionMapping setValues: failure\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("getFullKeyActionMapping"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getFullKeyActionMapping call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getFullKeyActionMapping call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("getSingleKeyActionMapping"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping getSingleKeyActionMapping call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping getSingleKeyActionMapping call - failed!\n"; + } + std::cout<<"result : "<Invoke(1000, + _T("cancelCodeDownload"), params, result); + std::cout<<"RemoteActionMapping Invoke ret : "<< ret <<"\n"; + result.ToString(res); + formatForDisplay(res); + if (result["success"].Boolean()) { + std::cout<<"RemoteActionMapping cancelCodeDownload call - Success!\n"; + } else { + std::cout<<"RemoteActionMapping cancelCodeDownload call - failed!\n"; + } + std::cout<<"result : "< "; + + if (std::cin.peek() == '\n') + std::getline(std::cin, cmd); + + std::getline(std::cin, cmd); + if (cmd.empty()) + continue; + else + break; + } + } + } + + return 0; +} diff --git a/ScreenCapture/CMakeLists.txt b/ScreenCapture/CMakeLists.txt new file mode 100644 index 0000000000..5ecf49f5be --- /dev/null +++ b/ScreenCapture/CMakeLists.txt @@ -0,0 +1,39 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME ScreenCapture) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + ScreenCapture.cpp + Module.cpp + ../helpers/tptimer.cpp) + +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +target_include_directories(${MODULE_NAME} PRIVATE ../helpers) + +target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins -lpng -lcurl) + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/ScreenCapture/Module.cpp b/ScreenCapture/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/ScreenCapture/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/ScreenCapture/Module.h b/ScreenCapture/Module.h new file mode 100644 index 0000000000..6516656a65 --- /dev/null +++ b/ScreenCapture/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME ScreenCapture +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/ScreenCapture/README.md b/ScreenCapture/README.md new file mode 100644 index 0000000000..8fd8ba2b1d --- /dev/null +++ b/ScreenCapture/README.md @@ -0,0 +1,15 @@ +----------------- +Build: + +bitbake wpeframework-service-plugins + +or + +bitbake thunder-plugins + +----------------- +Test: + +curl -d '{"jsonrpc":"2.0","id":"3","params": {"url":"http://10.0.0.233/upload.php"},"method": "com.comcast.ScreenCapture.1.uploadScreenCapture"}' http://127.0.0.1:9998/jsonrpc +curl -d '{"jsonrpc":"2.0","id":"3","params": {"url":"http://10.0.0.233/cgi-bin/upload.cgi", "callGUID": "test_guid"},"method": "com.comcast.ScreenCapture.1.uploadScreenCapture"}' http://127.0.0.1:9998/jsonrpc + diff --git a/ScreenCapture/ScreenCapture.config b/ScreenCapture/ScreenCapture.config new file mode 100644 index 0000000000..f30c209913 --- /dev/null +++ b/ScreenCapture/ScreenCapture.config @@ -0,0 +1,4 @@ +set (autostart false) +set (preconditions Platform) +set (callsign "com.comcast.ScreenCapture") + diff --git a/ScreenCapture/ScreenCapture.cpp b/ScreenCapture/ScreenCapture.cpp new file mode 100644 index 0000000000..377b47f623 --- /dev/null +++ b/ScreenCapture/ScreenCapture.cpp @@ -0,0 +1,497 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "ScreenCapture.h" + +#include "utils.h" + +#ifdef PLATFORM_BROADCOM +#include +#include +#endif + +#include +#include + +// Methods +#define METHOD_UPLOAD "uploadScreenCapture" + +// Events +#define EVT_UPLOAD_COMPLETE "uploadComplete" + +namespace WPEFramework +{ + namespace Plugin + { + SERVICE_REGISTRATION(ScreenCapture, 1, 0); + + ScreenCapture* ScreenCapture::_instance = nullptr; + + ScreenCapture::ScreenCapture() + : AbstractPlugin() + { + LOGINFO(); + ScreenCapture::_instance = this; + + screenShotDispatcher = new WPEFramework::Core::TimerType(64 * 1024, "ScreenCaptureDispatcher"); + + #ifdef PLATFORM_BROADCOM + inNexus = false; + #endif + + Register(METHOD_UPLOAD, &ScreenCapture::uploadScreenCapture, this); + } + + ScreenCapture::~ScreenCapture() + { + LOGINFO(); + ScreenCapture::_instance = nullptr; + + delete screenShotDispatcher; + } + + uint32_t ScreenCapture::uploadScreenCapture(const JsonObject& parameters, JsonObject& response) + { + std::lock_guard guard(m_callMutex); + + LOGINFO(); + + std::string callGUID; + + if(!parameters.HasLabel("url")) + { + response["message"] = "Upload url is not specified"; + + returnResponse(false); + } + + if(parameters.HasLabel("callGUID")) + callGUID = parameters["callGUID"].String(); + + screenShotDispatcher->Schedule( Core::Time::Now().Add(0), ScreenShotJob( this, parameters["url"].String(), callGUID ) ); + + returnResponse(true); + } + + uint64_t ScreenShotJob::Timed(const uint64_t scheduledTime) + { + if(!m_screenCapture) + { + LOGERR("!m_screenCapture"); + return 0; + } + + m_screenCapture->doUploadScreenCapture(url, callGUID); + + return 0; + } + + bool ScreenCapture::doUploadScreenCapture(std::string url, std::string callGUID) + { + std::vector png_data; + bool got_screenshot = false; + + #ifdef PLATFORM_BROADCOM + got_screenshot = getScreenshotNexus(png_data); + #endif + + #ifdef PLATFORM_INTEL + got_screenshot = getScreenshotIntel(png_data); + #endif + + if(got_screenshot) + { + std::string error_str; + + LOGWARN("uploading %d of png data to '%s'", png_data.size(), url.c_str() ); + + if(uploadDataToUrl(png_data, url.c_str(), error_str)) + { + JsonObject params; + params["status"] = true; + params["message"] = "Success"; + params["call_guid"] = callGUID; + + sendNotify(EVT_UPLOAD_COMPLETE, params); + + return true; + } + else + { + JsonObject params; + params["status"] = false; + params["message"] = std::string("Upload Failed: ") + error_str; + params["call_guid"] = callGUID; + + sendNotify(EVT_UPLOAD_COMPLETE, params); + + return false; + } + } + else + { + LOGERR("Error: could not get the screenshot"); + + JsonObject params; + params["status"] = false; + params["message"] = "Failed to get screen data"; + params["call_guid"] = callGUID; + + sendNotify(EVT_UPLOAD_COMPLETE, params); + + return false; + } + } + +#ifdef PLATFORM_INTEL + bool ScreenCapture::getScreenshotIntel(std::vector &png_out_data) + { + int i; + char *filename = "/proc/gdl/dump/wbp"; //both video and guide graphics, potentially at lower 720x480 +// char *filename = "/proc/gdl/dump/upp_d"; //graphics only, normally at higher 1280x720 +// char *filename = "/proc/gdl/dump/upp_a"; //video only, normally at higher 1280x720 + + FILE* fp = fopen(filename, "rb"); + + unsigned char info[56]; + fread(info, sizeof(unsigned char), 56, fp); // read the 54-byte header + + if(!fp) + { + LOGERR("Error: could not open image file '%s'", filename); + return false; + } + + // extract image height and width from header + int w = abs(*(int*)&info[18]); + int h = abs(*(int*)&info[22]); + + int size = 4 * w * h; + + LOGWARN("intel screenshot capture of size w:%d h:%d loaded", w, h); + + if(size < 1) + { + LOGERR("Error: png data size < 1"); + return false; + } + + std::vector data_v(size); + std::vector new_data_v(size); + + unsigned char* data = &data_v[0]; + unsigned char* new_data = &new_data_v[0]; + + fread(data, sizeof(unsigned char), size, fp); // read the rest of the data at once + fclose(fp); + + for(i = 0; i < size; i += 4) + { + //r and b need swapped? + new_data[i+0] = data[i+2]; + new_data[i+1] = data[i+1]; + new_data[i+2] = data[i+0]; + new_data[i+3] = data[i+3]; + } + + //convert to png + saveToPng(new_data, w, h, png_out_data); + + return true; + } +#endif + +#ifdef PLATFORM_BROADCOM + bool ScreenCapture::joinNexus() + { + if(inNexus) return true; + + NxClient_JoinSettings joinSettings; + + NxClient_GetDefaultJoinSettings(&joinSettings); + + snprintf(joinSettings.name, NXCLIENT_MAX_NAME, "%s", "wpeframework"); + + NEXUS_Error rc = NxClient_Join(&joinSettings); + + if (!(( rc == NEXUS_SUCCESS ))) + { + LOGERR("could not join Nexus"); + return false; + } + + LOGWARN("Nexus Joined"); + + inNexus = true; + + return true; + } + + bool ScreenCapture::getScreenshotNexus(std::vector &png_out_data) + { + if(!joinNexus()) + { + LOGERR("could not join Nexus"); + return false; + } + + NEXUS_Error err = NEXUS_SUCCESS; + bool res = true; + + NxClient_ScreenshotSettings screenshotSettings; +// NxClient_GetDefaultScreenshotSettings(&screenshotSettings); + memset(&screenshotSettings, 0, sizeof(screenshotSettings)); + + #ifdef SCREENCAP_SVP_ENABLED + screenshotSettings.screenshotWindow = NxClient_ScreenshotWindow_eGraphics; + LOGWARN("[SCREENCAP]: Using NxClient_ScreenshotWindow_eGraphics (graphics only, no video)"); + #else + screenshotSettings.screenshotWindow = NxClient_ScreenshotWindow_eAll; + LOGWARN("[SCREENCAP]: Using NxClient_ScreenshotWindow_eAll (graphics including video)"); + #endif + + NEXUS_SurfaceCreateSettings defSurfSettings; + memset(&defSurfSettings, 0, sizeof(defSurfSettings)); + NEXUS_Surface_GetDefaultCreateSettings( &defSurfSettings ); + + defSurfSettings.width = 1280; + defSurfSettings.height = 720; + //defSurfSettings.pixelFormat = NEXUS_PixelFormat_eA8_R8_G8_B8; + defSurfSettings.pixelFormat = NEXUS_PixelFormat_eA8_B8_G8_R8; + int bytesPerPixel = 4; + std::vector data_v(1280 * 720 * 4); + unsigned char *bytes = &data_v[0]; +// unsigned char bytes[1280 * 720 * 4]; + + + NEXUS_SurfaceHandle surface = NEXUS_Surface_Create( &defSurfSettings ); + + err = NxClient_Screenshot(&screenshotSettings, surface); + + if (err != NEXUS_SUCCESS) + { + LOGERR("[SCREENCAP]: Failed to get screenshot"); + res = false; + goto do_destroy_surface; + } + + NEXUS_SurfaceMemoryProperties properties; + NEXUS_Surface_GetMemoryProperties(surface, &properties); + + void* pSurfaceMemory; + err = NEXUS_Surface_Lock(surface, &pSurfaceMemory); + + if (err != NEXUS_SUCCESS) + { + LOGERR("[SCREENCAP]: Failed to lock surface"); + res = false; + goto do_destroy_surface; + } + else + { + LOGWARN("[SCREENCAP]: locked surface (pSurfaceMemory:%p pixelMemoryOffset:%d w:%d h:%d bpp:%d)", + pSurfaceMemory, properties.pixelMemoryOffset, defSurfSettings.width, defSurfSettings.height, bytesPerPixel); + } + + memcpy(bytes, (const char*) pSurfaceMemory + properties.pixelMemoryOffset, defSurfSettings.width * defSurfSettings.height * bytesPerPixel); + + NEXUS_Surface_Unlock( surface ); + + LOGWARN("[SCREENCAP]: unlocked surface"); + + do_destroy_surface: + NEXUS_Surface_Destroy( surface ); + + if(!res) + { + LOGERR("could not get screenshot from Nexus"); + return false; + } + + if(!saveToPng(bytes, defSurfSettings.width, defSurfSettings.height, png_out_data)) + { + LOGERR("could not convert Nexus screenshot to png"); + return false; + } + else + return true; + } +#endif + + static void PngWriteCallback(png_structp png_ptr, png_bytep data, png_size_t length) + { + std::vector *p = (std::vector*)png_get_io_ptr(png_ptr); + p->insert(p->end(), data, data + length); + } + + bool ScreenCapture::uploadDataToUrl(std::vector &data, const char *url, std::string &error_str) + { + CURL *curl; + CURLcode res; + bool call_succeeded = true; + + if(!url || !strlen(url)) + { + LOGERR("no url given"); + return false; + } + + LOGWARN("uploading png data of size %u to '%s'", data.size(), url); + + //init curl + curl_global_init(CURL_GLOBAL_ALL); + curl = curl_easy_init(); + + if(!curl) + { + LOGERR("could not init curl\n"); + return false; + } + + //create header + struct curl_slist *chunk = NULL; + chunk = curl_slist_append(chunk, "Content-Type: image/png"); + + //set url and data + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.size()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, &data[0]); + + //perform blocking upload call + res = curl_easy_perform(curl); + + //output success / failure log + if(CURLE_OK == res) + { + long response_code; + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + + if(600 > response_code && response_code >= 400) + { + LOGERR("uploading failed with response code %ld\n", response_code); + error_str = std::string("response code:") + std::to_string(response_code); + call_succeeded = false; + } + else + LOGWARN("upload done"); + } + else + { + LOGERR("upload failed with error %d:'%s'", res, curl_easy_strerror(res)); + error_str = std::to_string(res) + std::string(":'") + std::string(curl_easy_strerror(res)) + std::string("'"); + call_succeeded = false; + } + + //clean up curl object + curl_easy_cleanup(curl); + curl_slist_free_all(chunk); + + return call_succeeded; + } + + bool ScreenCapture::saveToPng(unsigned char *data, int width, int height, std::vector &png_out_data) + { + int bitdepth = 8; + int colortype = PNG_COLOR_TYPE_RGBA; + int pitch = 4 * width; + int transform = PNG_TRANSFORM_IDENTITY; + + int i = 0; + int r = 0; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + png_bytep* row_pointers = NULL; + + if (NULL == data) + { + LOGERR("Error: failed to save the png because the given data is NULL."); + r = -1; + goto error; + } + + if (0 == pitch) + { + LOGERR("Error: failed to save the png because the given pitch is 0."); + r = -3; + goto error; + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (NULL == png_ptr) + { + LOGERR("Error: failed to create the png write struct."); + r = -5; + goto error; + } + + info_ptr = png_create_info_struct(png_ptr); + if (NULL == info_ptr) + { + LOGERR("Error: failed to create the png info struct."); + r = -6; + goto error; + } + + png_set_IHDR(png_ptr, + info_ptr, + width, + height, + bitdepth, /* e.g. 8 */ + colortype, /* PNG_COLOR_TYPE_{GRAY, PALETTE, RGB, RGB_ALPHA, GRAY_ALPHA, RGBA, GA} */ + PNG_INTERLACE_NONE, /* PNG_INTERLACE_{NONE, ADAM7 } */ + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + + for (i = 0; i < height; ++i) + row_pointers[i] = data + i * pitch; + + png_set_write_fn(png_ptr, &png_out_data, PngWriteCallback, NULL); + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, transform, NULL); + + error: + + if (NULL != png_ptr) + { + + if (NULL == info_ptr) + { + LOGERR("Error: info ptr is null. not supposed to happen here.\n"); + } + + png_destroy_write_struct(&png_ptr, &info_ptr); + png_ptr = NULL; + info_ptr = NULL; + } + + if (NULL != row_pointers) + { + free(row_pointers); + row_pointers = NULL; + } + + return (r==0); + } + + } // namespace Plugin +} // namespace WPEFramework diff --git a/ScreenCapture/ScreenCapture.h b/ScreenCapture/ScreenCapture.h new file mode 100644 index 0000000000..dbafa28832 --- /dev/null +++ b/ScreenCapture/ScreenCapture.h @@ -0,0 +1,119 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include +#include + +#include "tptimer.h" + +#include "Module.h" +#include "utils.h" +#include "AbstractPlugin.h" + +namespace WPEFramework { + + namespace Plugin { + + class ScreenCapture; + + class ScreenShotJob + { + private: + ScreenShotJob() = delete; + ScreenShotJob& operator=(const ScreenShotJob& RHS) = delete; + + public: + ScreenShotJob(WPEFramework::Plugin::ScreenCapture* tpt, std::string _url, std::string _callGUID) : m_screenCapture(tpt), url(_url), callGUID(_callGUID) { } + ScreenShotJob(const ScreenShotJob& copy) : m_screenCapture(copy.m_screenCapture), url(copy.url), callGUID(copy.callGUID) { } + ~ScreenShotJob() {} + + inline bool operator==(const ScreenShotJob& RHS) const + { + return(m_screenCapture == RHS.m_screenCapture); + return(url == RHS.url); + return(callGUID == RHS.callGUID); + } + + public: + uint64_t Timed(const uint64_t scheduledTime); + + private: + WPEFramework::Plugin::ScreenCapture* m_screenCapture; + std::string url; + std::string callGUID; + }; + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class ScreenCapture : public AbstractPlugin { + private: + + // We do not allow this plugin to be copied !! + ScreenCapture(const ScreenCapture&) = delete; + ScreenCapture& operator=(const ScreenCapture&) = delete; + + //Begin methods + uint32_t uploadScreenCapture(const JsonObject& parameters, JsonObject& response); + //End methods + + #ifdef PLATFORM_BROADCOM + bool getScreenshotNexus(std::vector &png_data); + bool joinNexus(); + #endif + + #ifdef PLATFORM_INTEL + bool getScreenshotIntel(std::vector &png_data); + #endif + + bool saveToPng(unsigned char *bytes, int w, int h, std::vector &png_out_data); + bool uploadDataToUrl(std::vector &data, const char *url, std::string &error_str); + bool doUploadScreenCapture(std::string url, std::string callGUID); + + public: + ScreenCapture(); + virtual ~ScreenCapture(); + + public: + static ScreenCapture* _instance; + private: + std::mutex m_callMutex; + + WPEFramework::Core::TimerType *screenShotDispatcher; + + #ifdef PLATFORM_BROADCOM + bool inNexus; + #endif + + friend class ScreenShotJob; + }; + + } // namespace Plugin +} // namespace WPEFramework diff --git a/XCast/CMakeLists.txt b/XCast/CMakeLists.txt new file mode 100644 index 0000000000..4761bc3f16 --- /dev/null +++ b/XCast/CMakeLists.txt @@ -0,0 +1,43 @@ +# If not stated otherwise in this file or this component's license file the +# following copyright and licenses apply: +# +# Copyright 2020 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License 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. + +set(PLUGIN_NAME XCast) +set(MODULE_NAME ${NAMESPACE}${PLUGIN_NAME}) + +find_package(${NAMESPACE}Plugins REQUIRED) + +add_library(${MODULE_NAME} SHARED + XCast.cpp + Module.cpp + ../helpers/tptimer.cpp) + +find_package(RFC) +set_target_properties(${MODULE_NAME} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES) + +add_definitions (-DRT_PLATFORM_LINUX) +target_include_directories(${MODULE_NAME} PRIVATE ${RFC_INCLUDE_DIRS} ../helpers) +target_include_directories(${MODULE_NAME} PRIVATE $ENV{PKG_CONFIG_SYSROOT_DIR}/usr/include/pxcore) + +target_link_libraries(${MODULE_NAME} PRIVATE ${NAMESPACE}Plugins::${NAMESPACE}Plugins rtRemote rtCore ${RFC_LIBRARIES}) + + +install(TARGETS ${MODULE_NAME} + DESTINATION lib/${STORAGE_DIRECTORY}/plugins) + +write_config(${PLUGIN_NAME}) diff --git a/XCast/Module.cpp b/XCast/Module.cpp new file mode 100644 index 0000000000..69ecca0532 --- /dev/null +++ b/XCast/Module.cpp @@ -0,0 +1,22 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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 "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/XCast/Module.h b/XCast/Module.h new file mode 100644 index 0000000000..7f0d78bb33 --- /dev/null +++ b/XCast/Module.h @@ -0,0 +1,29 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once +#ifndef MODULE_NAME +#define MODULE_NAME DisplaySettings +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/XCast/README.md b/XCast/README.md new file mode 100644 index 0000000000..52abcc9bcc --- /dev/null +++ b/XCast/README.md @@ -0,0 +1,13 @@ +----------------- +Build: + +bitbake thunder-plugins + +----------------- +Test: + +curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.Xcast.1.getApiVersionNumber"}' http://127.0.0.1:9998/jsonrpc +{"jsonrpc":"2.0","id":3,"result":{"version":1,"success":true}} + curl --header "Content-Type: application/json" --request POST --data '{"jsonrpc":"2.0","id":"3","method": "com.comcast.Xcast.1.getQuirks"}' http://127.0.0.1:9998/jsonrpc +{"jsonrpc":"2.0","id":3,"result":{"quirks":[],"success":true}} + diff --git a/XCast/XCast.config b/XCast/XCast.config new file mode 100644 index 0000000000..ea9d09e4fc --- /dev/null +++ b/XCast/XCast.config @@ -0,0 +1,3 @@ +set (autostart false) +set (preconditions Platform) +set (callsign "com.comcast.Xcast") diff --git a/XCast/XCast.cpp b/XCast/XCast.cpp new file mode 100644 index 0000000000..1c4193205b --- /dev/null +++ b/XCast/XCast.cpp @@ -0,0 +1,451 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +//I have put several "TODO(MROLLINS)" in the code below to mark areas of concern I encountered +// when refactoring the servicemanager's version of displaysettings into this new thunder plugin format + +#include "XCast.h" +#include "tracing/Logging.h" +#include "utils.h" +#include "rfcapi.h" +#include +#include +using namespace std; + +// Events +// com.comcast.xcast_1 +#define EVT_ON_LAUNCH_REQUEST "onApplicationLaunchRequest" +#define EVT_ON_HIDE_REQUEST "onApplicationHideRequest" +#define EVT_ON_RESUME_REQUEST "onApplicationResumeRequest" +#define EVT_ON_STOP_REQUEST "onApplicationStopRequest" +#define EVT_ON_STATE_REQUEST "onApplicationStateRequest" +//Methods +#define METHOD_ON_APPLICATION_STATE_CHANGED "onApplicationStateChanged" +#define METHOD_GET_QUIRKS "getQuirks" +#define METHOD_GET_API_VERSION_NUMBER "getApiVersionNumber" + +#define LOCATE_CAST_FIRST_TIMEOUT_IN_MILLIS 5000 //5 seconds +#define LOCATE_CAST_SECOND_TIMEOUT_IN_MILLIS 15000 //15 seconds +#define LOCATE_CAST_THIRD_TIMEOUT_IN_MILLIS 30000 //30 seconds +#define LOCATE_CAST_FINAL_TIMEOUT_IN_MILLIS 60000 //60 seconds + +namespace WPEFramework { + + namespace Plugin { + + SERVICE_REGISTRATION(XCast, 1, 0); + + static int locateCastObjectRetryCount = 0; + static rtObjectRef xdialCastObj = NULL; + XCast * XCast::_instance = nullptr; + bool XCast::isCastEnabled = false; + + + static void remoteDisconnectCallback( void* data) { + char* serviceName = (char* ) data; + LOGINFO ( "remoteDisconnectCallback: Remote %s disconnected... ", serviceName); + XCast::_instance->onRtServiceDisconnected(); + } + + //XDIALCAST EVENT CALLBACK + static rtError onApplicationLaunchRequestCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + XCast::_instance->onXcastApplicationLaunchRequest(appObject); + } + else + rtLogError("*** Error: received unknown event"); + if (result) + *result = rtValue(true); + return RT_OK; + } + static rtError onApplicationStopRequestCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + XCast::_instance->onXcastApplicationStopRequest(appObject); + } + else + rtLogError("*** Error: received unknown event"); + if (result) + *result = rtValue(true); + return RT_OK; + } + static rtError onApplicationHideRequestCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + XCast::_instance->onXcastApplicationHideRequest(appObject); + } + else + rtLogError("*** Error: received unknown event"); + + if (result) + *result = rtValue(true); + + return RT_OK; + + } + static rtError onApplicationStateRequestCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + XCast::_instance->onXcastApplicationStateRequest(appObject); + } + else + rtLogError("*** Error: received unknown event"); + + if (result) + *result = rtValue(true); + + return RT_OK; + } + static rtError onApplicationResumeRequestCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + XCast::_instance->onXcastApplicationResumeRequest(appObject); + } + else + rtLogError("*** Error: received unknown event"); + + if (result) + *result = rtValue(true); + + return RT_OK; + } + static rtError onRtServiceByeCallback(int numArgs, const rtValue* args, rtValue* result, void* context) + { + if (numArgs == 1) + { + rtObjectRef appObject = args[0].toObject(); + rtString serviceName = appObject.get("serviceName"); + LOGINFO("Received RtService Bye Event! Service: %s", serviceName.cString()); + } + else + rtLogError("*** Error: received unknown event"); + + if (result) + *result = rtValue(true); + + return RT_OK; + + } + + void XCast::registerForXcastEvents(void *context) + { + LOGINFO("registerForXcastEvents: "); + + if(xdialCastObj != NULL) + { + rtError e = xdialCastObj.send("on", "onApplicationLaunchRequest" , new rtFunctionCallback(&onApplicationLaunchRequestCallback, context)); + LOGINFO("Registered onApplicationLaunchRequest ; response %d" ,e ); + e = xdialCastObj.send("on", "onApplicationStopRequest" , new rtFunctionCallback(&onApplicationStopRequestCallback, context)); + LOGINFO("Registered onApplicationStopRequest %d", e ); + e = xdialCastObj.send("on", "onApplicationHideRequest" , new rtFunctionCallback(&onApplicationHideRequestCallback, context)); + LOGINFO("Registered onApplicationHideRequest %d", e ); + e = xdialCastObj.send("on", "onApplicationResumeRequest" , new rtFunctionCallback(&onApplicationResumeRequestCallback, context)); + LOGINFO("Registered onApplicationResumeRequest %d", e ); + e = xdialCastObj.send("on", "onApplicationStateRequest" , new rtFunctionCallback(&onApplicationStateRequestCallback, context)); + LOGINFO("Registed onApplicationStateRequest %d", e ); + e = xdialCastObj.send("on", "bye" , new rtFunctionCallback(&onRtServiceByeCallback, context)); + LOGINFO("Registed rtService bye event %d", e ); + } + + } + + XCast::XCast() : AbstractPlugin() + , m_apiVersionNumber(1) + { + LOGINFO("New Instance"); + XCast::checkServiceStatus(); + if(XCast::isCastEnabled) + { + registerMethod(METHOD_GET_QUIRKS, &XCast::getQuirks, this); + registerMethod(METHOD_GET_API_VERSION_NUMBER, &XCast::getApiVersionNumber, this); + registerMethod(METHOD_ON_APPLICATION_STATE_CHANGED , &XCast::applicationStateChanged, this); + + m_locateCastTimer.connect( bind( &XCast::onLocateCastTimer, this )); + m_locateCastTimer.setSingleShot(true); + } + _instance = this; + + } + + XCast::~XCast() + { + LOGINFO("Dtr"); + Unregister(METHOD_GET_QUIRKS); + Unregister(METHOD_GET_API_VERSION_NUMBER); + Unregister(METHOD_ON_APPLICATION_STATE_CHANGED); + + if ( m_locateCastTimer.isActive()) + { + m_locateCastTimer.stop(); + } + } + + const string XCast::Initialize(PluginHost::IShell* /* service */) + { + LOGINFO("Activate"); + if (XCast::isCastEnabled) + { + rtError err; + rtRemoteEnvironment* env = rtEnvironmentGetGlobal(); + err = rtRemoteInit(env); + + if(err != RT_OK){ + std::cout<<"Xcastservice: rtRemoteInit failed"<("applicationName"); + if (!strcmp(appName.cString(),"Netflix")) + appName = "NetflixApp"; + rtString rtparams = appObject.get("parameters"); + LOGINFO ("Received ApplicationLaunchRequest AppName: %s Params: %s ", appName.cString(), rtparams.cString()); + + JsonObject params; + params["applicationName"] = appName.cString(); + + if (appName == "NetflixApp") + params["pluginUrl"]=rtparams.cString(); + if (appName == "YouTube") + params["url"]=rtparams.cString(); + + params["parameters"]= rtparams.cString(); + + sendNotify(EVT_ON_LAUNCH_REQUEST, params); + } + void XCast::onXcastApplicationStopRequest(rtObjectRef appObject) + { + LOGINFO("XcastService::onXcastApplicationStopRequest "); + rtString appName = appObject.get("applicationName"); + if (!strcmp(appName.cString(),"Netflix")) + appName = "NetflixApp"; + + rtString appID = appObject.get("applicationId"); + LOGINFO("Received ApplicationStopRequest AppName: %s App Id : %s", appName.cString() , appID.cString()); + + JsonObject params; + params["applicationName"] = appName.cString(); + params["applicationId"]= appID.cString(); + + sendNotify(EVT_ON_STOP_REQUEST, params); + } + void XCast::onXcastApplicationHideRequest(rtObjectRef appObject) + { + LOGINFO("XcastService::onXcastApplicationHideRequest : "); + rtString appName = appObject.get("applicationName"); + if (!strcmp(appName.cString(),"Netflix")) + appName = "NetflixApp"; + rtString appID = appObject.get("applicationId"); + LOGINFO("Received ApplicationHideRequest AppName: %s AppID: %s", appName.cString(), appID.cString()); + + + JsonObject params; + params["applicationName"] = appName.cString(); + params["applicationId"]= appID.cString(); + + sendNotify(EVT_ON_HIDE_REQUEST, params); + } + void XCast::onXcastApplicationStateRequest(rtObjectRef appObject) + { + LOGINFO("XcastService::onXcastApplicationStateRequest: "); + rtString appName = appObject.get("applicationName"); + if (!strcmp(appName.cString(),"Netflix")) + appName = "NetflixApp"; + + rtString appID = appObject.get("applicationId"); + LOGINFO("Received onXcastApplicationStateRequest AppName: %s AppID: %s", appName.cString(), appID.cString()); + + JsonObject params; + params["applicationName"] = appName.cString(); + params["applicationId"]= appID.cString(); + + string json_str; + params.ToString(json_str); + sendNotify(EVT_ON_STATE_REQUEST , params); + + } + void XCast::onXcastApplicationResumeRequest(rtObjectRef appObject) + { + LOGINFO("XcastService::onXcastApplicationResumeRequest "); + rtString appName = appObject.get("applicationName"); + if (!strcmp(appName.cString(),"Netflix")) + appName = "NetflixApp"; + rtString appID = appObject.get("applicationId"); + LOGINFO("Received ApplicationResumeRequest AppName: %s AppID: %s" , appName.cString(), appID.cString()); + + JsonObject params; + params["applicationName"] = appName.cString(); + params["applicationId"]= appID.cString(); + sendNotify(EVT_ON_RESUME_REQUEST, params); + } + + bool XCast::checkServiceStatus() + { + LOGINFO(); + RFC_ParamData_t param; + WDMP_STATUS wdmpStatus = getRFCParameter(const_cast("Xcast"), "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.XDial.Enable", ¶m); + if (wdmpStatus == WDMP_SUCCESS || wdmpStatus == WDMP_ERR_DEFAULT_VALUE) + { + if( param.type == WDMP_BOOLEAN ) + { + if(strncasecmp(param.value,"true",4) == 0 ) + isCastEnabled = true; + } + } + LOGINFO(" Is cast enabled ? %d , call value %d ", isCastEnabled, wdmpStatus); + return isCastEnabled; + } + } // namespace Plugin +} // namespace WPEFramework diff --git a/XCast/XCast.h b/XCast/XCast.h new file mode 100644 index 0000000000..584c142f42 --- /dev/null +++ b/XCast/XCast.h @@ -0,0 +1,96 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2020 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License 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. +**/ + +#pragma once + +#include "tptimer.h" +#include "Module.h" +#include "utils.h" +#include +#include +#include +#include "AbstractPlugin.h" + + +namespace WPEFramework { + + namespace Plugin { + + // This is a server for a JSONRPC communication channel. + // For a plugin to be capable to handle JSONRPC, inherit from PluginHost::JSONRPC. + // By inheriting from this class, the plugin realizes the interface PluginHost::IDispatcher. + // This realization of this interface implements, by default, the following methods on this plugin + // - exists + // - register + // - unregister + // Any other methood to be handled by this plugin can be added can be added by using the + // templated methods Register on the PluginHost::JSONRPC class. + // As the registration/unregistration of notifications is realized by the class PluginHost::JSONRPC, + // this class exposes a public method called, Notify(), using this methods, all subscribed clients + // will receive a JSONRPC message as a notification, in case this method is called. + class XCast : public AbstractPlugin { + private: + + // We do not allow this plugin to be copied !! + XCast(const XCast&) = delete; + XCast& operator=(const XCast&) = delete; + + //Begin methods + uint32_t getQuirks(const JsonObject& parameters, JsonObject& response); + uint32_t getApiVersionNumber(const JsonObject& parameters, JsonObject& response); + uint32_t applicationStateChanged(const JsonObject& parameters, JsonObject& response); + //End methods + + //Begin events + + //End events + public: + XCast(); + virtual ~XCast(); + static XCast * _instance; + //Build QueryInterface implementation, specifying all possible interfaces to be returned. + BEGIN_INTERFACE_MAP(XCast) + INTERFACE_ENTRY(PluginHost::IPlugin) + INTERFACE_ENTRY(PluginHost::IDispatcher) + END_INTERFACE_MAP + //IPlugin methods + virtual const string Initialize(PluginHost::IShell* service) override; + virtual void Deinitialize(PluginHost::IShell* service) override; + virtual string Information() const override; + + void onRtServiceDisconnected(void); + void onXcastApplicationLaunchRequest(rtObjectRef); + void onXcastApplicationStopRequest(rtObjectRef); + void onXcastApplicationHideRequest(rtObjectRef); + void onXcastApplicationResumeRequest(rtObjectRef); + void onXcastApplicationStateRequest(rtObjectRef); + private: + static bool isCastEnabled; + uint32_t m_apiVersionNumber; + //Timer related variables and functions + TpTimer m_locateCastTimer; + + //Internal methods + void onLocateCastTimer(); + void registerForXcastEvents(void *context); + + static bool checkServiceStatus(); + }; + } // namespace Plugin +} // namespace WPEFramework