diff --git a/CHANGELOG.md b/CHANGELOG.md index 6328f828d..b488ee233 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,21 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.5.0](https://github.com/rdkcentral/tr69hostif/compare/1.4.9...1.5.0) + +- update license year and doxygen comment [`#521`](https://github.com/rdkcentral/tr69hostif/pull/521) +- RDKEMW-19163:Migrate to Existing Thunder Plugin for libds Methods and… [`#502`](https://github.com/rdkcentral/tr69hostif/pull/502) +- Merge tag '1.4.9' into develop [`34bc947`](https://github.com/rdkcentral/tr69hostif/commit/34bc9478d193ee19f3728dc423b75561a0f83f5a) + #### [1.4.9](https://github.com/rdkcentral/tr69hostif/compare/1.4.8...1.4.9) +> 21 July 2026 + - RDK-61871: Add OTEL source code and recipe changes to RDKE [`#511`](https://github.com/rdkcentral/tr69hostif/pull/511) - RDKEMW-20790 : L2 Coverage for tr69hostif update [`#513`](https://github.com/rdkcentral/tr69hostif/pull/513) - RDKEMW-20790 : Improve L2 Coverage for tr69hostif [`#507`](https://github.com/rdkcentral/tr69hostif/pull/507) - RDKEMW-21374: Fix L2 Upload Results to Automatics Error [`#508`](https://github.com/rdkcentral/tr69hostif/pull/508) +- tr69hostif 1.4.9 release changelog updates [`ea476f9`](https://github.com/rdkcentral/tr69hostif/commit/ea476f92ec83bc7141d174e79196a12e2f60ada8) - Merge tag '1.4.8' into develop [`78e90eb`](https://github.com/rdkcentral/tr69hostif/commit/78e90eb7cfb0bfe74597e3be7d80f9a0af761c73) #### [1.4.8](https://github.com/rdkcentral/tr69hostif/compare/1.4.7...1.4.8) diff --git a/configure.ac b/configure.ac index 1754514c4..8d9f1a171 100644 --- a/configure.ac +++ b/configure.ac @@ -387,6 +387,19 @@ AC_ARG_ENABLE([libsoup3], esac],[libsoup3=false]) AM_CONDITIONAL([LIBSOUP3_ENABLE], [test x$libsoup3 = xtrue]) +# Enable Thunder-backed STBService implementation +AC_ARG_ENABLE([thunder], + AS_HELP_STRING([--enable-thunder],[enable Thunder-backed STBService implementation (default is no)]), + [ + case "${enableval}" in + yes) THUNDER_CLIENT_ENABLE=true ;; + no) THUNDER_CLIENT_ENABLE=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-thunder]) ;; + esac + ], + [THUNDER_CLIENT_ENABLE=false; echo "Thunder STBService client is disabled"]) +AM_CONDITIONAL([WITH_THUNDER_CLIENT], [test x$THUNDER_CLIENT_ENABLE = xtrue]) + AM_CONDITIONAL([WITH_MOCA_PROFILE], [test "x$enable_moca" = "xyes"]) AM_CONDITIONAL([WITH_MOCA20], [test "x$enable_moca2" = "xyes"]) AM_CONDITIONAL([WITH_WIFI_PROFILE], [test x$WIFI_PROFILE_ENABLE = xtrue]) diff --git a/cov_build.sh b/cov_build.sh index 23fe41a19..d66a1a6a8 100644 --- a/cov_build.sh +++ b/cov_build.sh @@ -92,10 +92,10 @@ cd $WORKDIR sed -i '/PKG_CHECK_MODULES(\[PROCPS\], \[libproc >= 3.2.8\])/s/^/#/' ./configure.ac autoreconf -i -./configure --enable-IPv6=yes --enable-wifi=yes +./configure --enable-IPv6=yes --enable-wifi=yes --enable-thunder=yes make AM_CXXFLAGS="-I$WORKDIR/src/unittest/stubs -I$WORKDIR/src/hostif/include -I/usr/include/cjson -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$WORKDIR/src/hostif/handlers/include -I$WORKDIR/src/hostif/parodusClient/waldb -I$WORKDIR/src/hostif/profiles/DeviceInfo -I/usr/include/cjson -I$WORKDIR/src/hostif/profiles/Time -I$WORKDIR/src/hostif/profiles/Device -I/usr/include/libsoup-3.0 -I/usr/include/yajl -I$WORKDIR/src/hostif/profiles/STBService -I$WORKDIR/src/unittest/stubs/ds -I/usr/devicesettings/ds -I/usr/local/include -I$WORKDIR/src/hostif/profiles/IP -I$WORKDIR/src/hostif/profiles/Ethernet -I$WORKDIR/src/hostif/profiles/wifi -I/usr/local/include/rbus -I$WORKDIR/src/hostif/parodusClient/pal -I/usr/rdk-halif-device_settings/include -I/usr/local/include/libparodus -I/usr/local/include -I/usr/rdkvhal-devicesettings-raspberrypi4 -I/usr/local/include/ -I/usr/include/yajl -I/usr/tinyxml2 -I/usr/devicesettings/ds -I/$WORKDIR/src/hostif/httpserver/include -I/usr/remote_debugger/src/ -DIPV6_SUPPORT -DUSE_WIFI_PROFILE -DMEDIA_CLIENT -DPRIVACYMODES_CONTROL" \ -AM_LDFLAGS="-L/usr/local/lib -lrbus -lsecure_wrapper -lcurl -lrfcapi -lrdkloggers -llibparodus -lglib-2.0 -lnanomsg -lIARMBus -lWPEFrameworkPowerController -lds -ldshalcli -ldshalsrv -lwrp-c -lwdmp-c -lprocps -ltrower-base64 -lcimplog -lsoup-3.0 -L/usr/lib/x86_64-linux-gnu -lyajl -L/usr/local/lib/x86_64-linux-gnu -ltinyxml2" CXXFLAGS="-fpermissive -DPARODUS_ENABLE -DUSE_REMOTE_DEBUGGER" \ +AM_LDFLAGS="-L/usr/local/lib -lrbus -lsecure_wrapper -lcurl -lrfcapi -lrdkloggers -llibparodus -lglib-2.0 -lnanomsg -lIARMBus -lWPEFrameworkPowerController -lwrp-c -lwdmp-c -lprocps -ltrower-base64 -lcimplog -lsoup-3.0 -L/usr/lib/x86_64-linux-gnu -lyajl -L/usr/local/lib/x86_64-linux-gnu -ltinyxml2" CXXFLAGS="-fpermissive -DPARODUS_ENABLE -DUSE_REMOTE_DEBUGGER -DUSE_THUNDER_CLIENT" \ install cd ./src/hostif/parodusClient/pal/mock-parodus/ diff --git a/docs/troubleshooting/common-errors.md b/docs/troubleshooting/common-errors.md index 4c10b021c..2ca5a2b1a 100644 --- a/docs/troubleshooting/common-errors.md +++ b/docs/troubleshooting/common-errors.md @@ -94,6 +94,38 @@ The update handler uses a polling loop and sleeps for 60 seconds between passes. - verify the affected profile participates in `registerUpdateCallback()` and `checkForUpdates()` - account for the poll interval when interpreting latency +## STBService Thunder Method Not Found + +### Symptom + +STBService GET/SET requests begin returning backend failures after migration to Thunder-backed handlers. + +### Why it happens + +The selected plugin method name or request key does not match the Thunder plugin interface (for example wrong callsign, wrong field name, or wrong port argument key). + +### What to check + +- verify the method string includes the full callsign and method name (for example `org.rdk.DisplaySettings.getVolumeLevel`) +- verify request keys match plugin expectations (`audioPort`, `videoDisplay`, or no port key for singleton domains) +- verify response field extraction uses the correct typed field name + +## STBService Instance Count Drift After Thunder Migration + +### Symptom + +Component table instance counts differ from previous behavior (for example AudioOutput/SPDIF/HDMI instance numbers change). + +### Why it happens + +Port-based components now derive instances from Thunder port discovery, and plugin-reported port lists can differ from legacy DS HAL ordering or naming. + +### What to check + +- verify port enumeration result from Thunder (`getSupportedAudioPorts` or `getSupportedVideoDisplays`) +- verify component-level filtering rules (for example SPDIF-only filtering) are applied consistently +- verify invalid instance handling returns an explicit invalid-parameter style fault + ## See Also - [Threading Model](../architecture/threading-model.md) diff --git a/run_l2.sh b/run_l2.sh index ca0ba69e8..ad43b8da2 100644 --- a/run_l2.sh +++ b/run_l2.sh @@ -95,6 +95,7 @@ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/webpa_ pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/custom.json test/functional-tests/tests/tr69hostif_custom.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/dhcpv4.json test/functional-tests/tests/tr69hostif_dhcpv4.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/moca.json test/functional-tests/tests/tr69hostif_moca.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/stbservice_thunder.json test/functional-tests/tests/tr69hostif_stbservice_thunder.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/device_info.json test/functional-tests/tests/tr69hostif_device_info.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/interfacestack.json test/functional-tests/tests/tr69hostif_interfacestack.py diff --git a/run_ut.sh b/run_ut.sh index 23a4b3e65..7e0b7938f 100644 --- a/run_ut.sh +++ b/run_ut.sh @@ -42,6 +42,9 @@ if [ "x$1" = "x--enable-cov" ]; then ENABLE_COV=true fi +# Force Thunder mode for production builds +CONFIGURE_THUNDER_OPT="--enable-thunder=yes" + apt-get update apt-get -y install libtinyxml2-dev apt-get -y install libsoup-3.0-dev @@ -171,12 +174,30 @@ make ./devieInfo_gtest echo "********************" +echo "**** Compiling STBService Thunder gtest ****" +cd $TOP_DIR/src/hostif/profiles/STBService/gtest +rm -f stbservice_thunder_gtest +make || { echo "ERROR: STBService Thunder gtest build failed"; exit 1; } +./stbservice_thunder_gtest || { echo "ERROR: STBService Thunder gtest execution failed"; exit 1; } +echo "********************" + cd $TOP_DIR if [ "$ENABLE_COV" = true ]; then lcov --capture --directory . --output-file coverage.info lcov --remove coverage.info '/usr/*' '*/gtest/*' '*/mocks/*' --output-file filtered.info - lcov --extract filtered.info '*/src/hostif/httpserver/*' '*/src/hostif/parodusClient/*' '*/src/hostif/src/*' '*/src/hostif/profiles/DHCPv4/*' '*/src/hostif/profiles/Device/*' '*/src/hostif/profiles/DeviceInfo/*' '*/src/hostif/profiles/Ethernet/*' '*/src/hostif/profiles/Time/*' --output-file tr69hostif_coverage.info + lcov --extract filtered.info '*/src/hostif/httpserver/*' '*/src/hostif/parodusClient/*' '*/src/hostif/src/*' '*/src/hostif/profiles/DHCPv4/*' '*/src/hostif/profiles/Device/*' '*/src/hostif/profiles/DeviceInfo/*' '*/src/hostif/profiles/Ethernet/*' '*/src/hostif/profiles/Time/*' '*/src/hostif/profiles/STBService/*' --output-file tr69hostif_coverage_temp.info + # Remove non-Thunder STBService files (libds versions no longer used) and untested Thunder files + lcov --remove tr69hostif_coverage_temp.info \ + '*/STBService/Components_AudioOutput.cpp' \ + '*/STBService/Components_DisplayDevice.cpp' \ + '*/STBService/Components_HDMI.cpp' \ + '*/STBService/Components_SPDIF.cpp' \ + '*/STBService/Components_VideoDecoder.cpp' \ + '*/STBService/Components_VideoOutput.cpp' \ + '*/STBService/Capabilities.cpp' \ + --output-file tr69hostif_coverage.info + rm -f tr69hostif_coverage_temp.info lcov --list tr69hostif_coverage.info fi diff --git a/src/Makefile.am b/src/Makefile.am index d26b2a3f3..142dacd91 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -81,14 +81,18 @@ if XRELIB_FLAG AM_LDFLAGS = $(GLIB_LDFLAGS) $(GLIB_LIBS) \ $(G_THREAD_LIBS) -lyajl $(SOUP_LIBS) \ $(NEXUS_LIB) -ldbus-1 \ - -lIARMBus -lrdkloggers\ - -lds -ldshalcli -ltr69ProfileXcaliber -lrbus + -lIARMBus -lrdkloggers \ + -ltr69ProfileXcaliber -lrbus else AM_LDFLAGS = $(GLIB_LDFLAGS) $(GLIB_LIBS) \ $(G_THREAD_LIBS) -lyajl $(SOUP_LIBS) \ $(NEXUS_LIB) -ldbus-1 \ - -lIARMBus -lrdkloggers\ -lrbus - -lds -ldshalcli + -lIARMBus -lrdkloggers -lrbus +endif + +if WITH_THUNDER_CLIENT +else +AM_LDFLAGS += -lds -ldshalcli endif if POWERCONTROLLER_ENABLE diff --git a/src/configure.ac b/src/configure.ac index 5a4d58d68..42a904c7c 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -70,6 +70,7 @@ AC_SUBST(T2_EVENT_FLAG) hostif/profiles/Time/gtest/Makefile hostif/profiles/DeviceInfo/gtest/Makefile hostif/handlers/src/gtest/Makefile + hostif/profiles/STBService/gtest/Makefile ]) # Generate the configure script diff --git a/src/hostif/handlers/Makefile.am b/src/hostif/handlers/Makefile.am index 19f5ecd69..98f324ded 100644 --- a/src/hostif/handlers/Makefile.am +++ b/src/hostif/handlers/Makefile.am @@ -41,8 +41,6 @@ AM_CXXFLAGS = -I$(top_srcdir)/src/hostif/include \ $(WIFI_PROFILE_FLAG) $(XRDK_SDCARD_PROFILE_FLAG) $(XRDK_EMMC_PROFILE_FLAG) \ -I=/usr/include/rdk/iarmbus/ \ -I=/usr/include/rdk/iarmmgrs/sysmgr/ \ - -I=/usr/include/rdk/ds/ \ - -I=/usr/include/rdk/ds-hal/ \ -I$(PKG_CONFIG_SYSROOT_DIR)/usr/include/rbus/ \ -I=/usr/include/libsoup-3.0/ @@ -74,7 +72,16 @@ AM_CXXFLAGS += -DBTMGR_ENABLE_IARM_INTERFACE AM_CXXFLAGS += -I$(top_srcdir)/src/hostif/profiles/DeviceInfo endif -AM_LDFLAGS = $(GLIB_LIBS) $(G_THREAD_LIBS) $(SOUP_LIBS) $(PROCPS_LIBS) -lIARMBus -lyajl -lds -ldshalcli -ldbus-1 -lsoup-3.0 -lgobject-2.0 -lsecure_wrapper +AM_LDFLAGS = $(GLIB_LIBS) $(G_THREAD_LIBS) $(SOUP_LIBS) $(PROCPS_LIBS) -lIARMBus -lyajl -ldbus-1 -lsoup-3.0 -lgobject-2.0 -lsecure_wrapper + +if !WITH_THUNDER_CLIENT +AM_CXXFLAGS += -I=/usr/include/rdk/ds/ \ + -I=/usr/include/rdk/ds-hal/ +AM_LDFLAGS += -lds -ldshalcli +endif +if WITH_THUNDER_CLIENT +AM_CXXFLAGS += -DUSE_THUNDER_CLIENT +endif if WIFI_CLIENT_ROAMING AM_CXXFLAGS += -DWIFI_CLIENT_ROAMING endif diff --git a/src/hostif/handlers/src/gtest/Makefile.am b/src/hostif/handlers/src/gtest/Makefile.am index 279fb4966..d1ff02945 100644 --- a/src/hostif/handlers/src/gtest/Makefile.am +++ b/src/hostif/handlers/src/gtest/Makefile.am @@ -23,7 +23,7 @@ bin_PROGRAMS = handlers_gtest # Define the include directories -COMMON_CPPFLAGS = -std=c++11 -DGTEST_ENABLE -DYOCTO_BUILD -DUSE_DEV_PROPERTIES_CONF -DUSE_REMOTE_DEBUGGER -I/usr/include -I/usr/include/cjson -I$(TOP_DIR)/src/hostif/include -I$(TOP_DIR)/src/hostif/profiles/DeviceInfo -I$(TOP_DIR)/src/unittest/stubs -I$(TOP_DIR)/src/unittest/stubs/ds -I$(TOP_DIR)/src/unittest/stubs/rbus/include -I$(TOP_DIR)/src/hostif/handlers/include -I$(TOP_DIR)/src/hostif/parodusClient/pal -I$(TOP_DIR)/src/hostif/parodusClient/waldb -I$(TOP_DIR)/src/hostif/httpserver/include -I$(TOP_DIR)/src/hostif/handlers/src -I/usr/include/rbus -I/usr/local/include/rbus -Isrc/unittest/stubs/rbus/include/ -I$(TOP_DIR)/src/hostif/profiles/Time -I$(TOP_DIR)/src/hostif/profiles/Device -I$(TOP_DIR)/src/hostif/profiles/IP -I$(TOP_DIR)/src/hostif/profiles/STBService -I/usr/rdk-halif-device_settings/include/ -I/usr/rdkvhal-devicesettings-raspberrypi4/ -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$(TOP_DIR)/src/hostif/profiles/Ethernet -I/usr/remote_debugger/src/ -I/usr/local/include/libparodus/ -I/usr/local/include/wrp-c/ -I$(TOP_DIR)/src/hostif/parodusClient/startParodus/ -I$(TOP_DIR)/src/hostif/profiles/DHCPv4 -I/usr/include/libsoup-3.0 +COMMON_CPPFLAGS = -std=c++11 -DGTEST_ENABLE -DYOCTO_BUILD -DUSE_DEV_PROPERTIES_CONF -DUSE_REMOTE_DEBUGGER -DUSE_THUNDER_CLIENT -I/usr/include -I/usr/include/cjson -I$(TOP_DIR)/src/hostif/include -I$(TOP_DIR)/src/hostif/profiles/DeviceInfo -I$(TOP_DIR)/src/unittest/stubs -I$(TOP_DIR)/src/unittest/stubs/rbus/include -I$(TOP_DIR)/src/hostif/handlers/include -I$(TOP_DIR)/src/hostif/parodusClient/pal -I$(TOP_DIR)/src/hostif/parodusClient/waldb -I$(TOP_DIR)/src/hostif/httpserver/include -I$(TOP_DIR)/src/hostif/handlers/src -I/usr/include/rbus -I/usr/local/include/rbus -Isrc/unittest/stubs/rbus/include/ -I$(TOP_DIR)/src/hostif/profiles/Time -I$(TOP_DIR)/src/hostif/profiles/Device -I$(TOP_DIR)/src/hostif/profiles/IP -I$(TOP_DIR)/src/hostif/profiles/STBService -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I$(TOP_DIR)/src/hostif/profiles/Ethernet -I/usr/remote_debugger/src/ -I/usr/local/include/libparodus/ -I/usr/local/include/wrp-c/ -I$(TOP_DIR)/src/hostif/parodusClient/startParodus/ -I$(TOP_DIR)/src/hostif/profiles/DHCPv4 -I/usr/include/libsoup-3.0 # Define the libraries to link against COMMON_LDADD = -lgtest -lgtest_main -lgmock_main -lgmock -lgcov $(GLIB_LIBS) -ltinyxml2 -lcjson -lcurl -lglib-2.0 -llibparodus -lwrp-c -lnanomsg -lmsgpackc -ltrower-base64 -lcimplog -lsoup-3.0 @@ -31,7 +31,7 @@ COMMON_LDADD = -lgtest -lgtest_main -lgmock_main -lgmock -lgcov $(GLIB_LIBS) -l # Define the compiler flags COMMON_CXXFLAGS = -frtti $(GLIB_CFLAGS) -fprofile-arcs -ftest-coverage -handlers_gtest_SOURCES = handlers_test.cpp $(TOP_DIR)/src/hostif/parodusClient/startParodus/startParodus.cpp $(TOP_DIR)/src/hostif/src/hostIf_utils.cpp $(TOP_DIR)/src/hostif/src/IniFile.cpp $(TOP_DIR)/src/unittest/stubs/secure_wrapper.c $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComRFCStore.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComRFC.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStoreJournal.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_NotificationHandler.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_notification.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_attribute.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_parameter.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/libpd.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_adapter.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_msgHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_XrdkCentralT2_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_IPClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_ProcessStatus_Process.cpp $(TOP_DIR)/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_DisplayDevice.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_EthernetClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/Ethernet/Device_Ethernet_Interface_Stats.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_TimeClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface.cpp $(TOP_DIR)/src/hostif/profiles/Time/Device_Time.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_updateHandler.cpp $(TOP_DIR)/src/hostif/profiles/Device/x_rdk_profile.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Capabilities.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_SPDIF.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoDecoder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_AudioOutput.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_HDMI.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoOutput.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface_Stats.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_ActivePort.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Diagnostics_IPPing.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface_IPv4Address.cpp $(TOP_DIR)/src/hostif/handlers/src/x_rdk_req_handler.cpp $(TOP_DIR)/src/unittest/stubs/dm_stubs.cpp $(TOP_DIR)/src/hostif/parodusClient/waldb/waldb.cpp $(TOP_DIR)/src/unittest/stubs/wdmp-c.c $(TOP_DIR)/src/unittest/stubs/wdmp_internal.c $(TOP_DIR)/src/hostif/httpserver/src/http_server.cpp $(TOP_DIR)/src/hostif/httpserver/src/request_handler.cpp $(TOP_DIR)/src/hostif/httpserver/src/XrdkCentralComRFCVar.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_Processor.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_ProcessStatus.cpp $(TOP_DIR)/src/unittest/stubs/ds/libprocps.cpp $(TOP_DIR)/src/unittest/stubs/file_writer.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_DHCPv4Client_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/DHCPv4/Device_DHCPv4_Client.cpp +handlers_gtest_SOURCES = handlers_test.cpp $(TOP_DIR)/src/hostif/parodusClient/startParodus/startParodus.cpp $(TOP_DIR)/src/hostif/src/hostIf_utils.cpp $(TOP_DIR)/src/hostif/src/IniFile.cpp $(TOP_DIR)/src/unittest/stubs/secure_wrapper.c $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComRFCStore.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComRFC.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStore.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/XrdkCentralComBSStoreJournal.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_NotificationHandler.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_notification.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_attribute.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_parameter.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/libpd.cpp $(TOP_DIR)/src/hostif/parodusClient/pal/webpa_adapter.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_msgHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_XrdkCentralT2_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_IPClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_ProcessStatus_Process.cpp $(TOP_DIR)/src/hostif/profiles/Ethernet/Device_Ethernet_Interface.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_DisplayDevice_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_HDMI_Thunder.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_EthernetClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/Ethernet/Device_Ethernet_Interface_Stats.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_TimeClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface.cpp $(TOP_DIR)/src/hostif/profiles/Time/Device_Time.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_updateHandler.cpp $(TOP_DIR)/src/hostif/profiles/Device/x_rdk_profile.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Capabilities_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_SPDIF_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoDecoder_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_AudioOutput_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoOutput_Thunder.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface_Stats.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_ActivePort.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Diagnostics_IPPing.cpp $(TOP_DIR)/src/hostif/profiles/IP/Device_IP_Interface_IPv4Address.cpp $(TOP_DIR)/src/hostif/handlers/src/x_rdk_req_handler.cpp $(TOP_DIR)/src/unittest/stubs/dm_stubs.cpp $(TOP_DIR)/src/hostif/parodusClient/waldb/waldb.cpp $(TOP_DIR)/src/unittest/stubs/wdmp-c.c $(TOP_DIR)/src/unittest/stubs/wdmp_internal.c $(TOP_DIR)/src/hostif/httpserver/src/http_server.cpp $(TOP_DIR)/src/hostif/httpserver/src/request_handler.cpp $(TOP_DIR)/src/hostif/httpserver/src/XrdkCentralComRFCVar.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_Processor.cpp $(TOP_DIR)/src/hostif/profiles/DeviceInfo/Device_DeviceInfo_ProcessStatus.cpp $(TOP_DIR)/src/unittest/stubs/ds/libprocps.cpp $(TOP_DIR)/src/unittest/stubs/file_writer.cpp $(TOP_DIR)/src/hostif/handlers/src/hostIf_DHCPv4Client_ReqHandler.cpp $(TOP_DIR)/src/hostif/profiles/DHCPv4/Device_DHCPv4_Client.cpp # Apply common properties to each program handlers_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) diff --git a/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp b/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp index 51104e4d6..ee84dab77 100644 --- a/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp +++ b/src/hostif/handlers/src/hostIf_DeviceClient_ReqHandler.cpp @@ -34,8 +34,6 @@ #include "hostIf_main.h" #include "hostIf_DeviceClient_ReqHandler.h" #include "hostIf_utils.h" -#include "host.hpp" -#include "dsError.h" #include "libIBus.h" #include "Device_DeviceInfo.h" #include "Device_DeviceInfo_Processor.h" diff --git a/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp b/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp index b4321b7f2..4df4c7d0f 100644 --- a/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp +++ b/src/hostif/handlers/src/hostIf_dsClient_ReqHandler.cpp @@ -47,9 +47,11 @@ #include "Components_VideoOutput.h" #include "Components_VideoDecoder.h" #include "hostIf_utils.h" +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "manager.hpp" #include "dsError.h" +#endif /* USE_THUNDER_CLIENT */ #include "libIBus.h" #define CAPABILTIES_OBJ "Device.Services.STBService.1.Capabilities." @@ -79,6 +81,12 @@ bool DSClientReqHandler::init() { RDK_LOG(RDK_LOG_TRACE1,LOG_TR69HOSTIF,"[%s:%s] Entering..\n", __FUNCTION__, __FILE__); RDK_LOG(RDK_LOG_DEBUG,LOG_TR69HOSTIF,"[%s()] Device manager Initializing\n", __FUNCTION__); +#ifdef USE_THUNDER_CLIENT + RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s()] STBService interface: Thunder (WPEFramework)\n", __FUNCTION__); +#else + RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s()] STBService interface: IARM/libds\n", __FUNCTION__); +#endif +#ifndef USE_THUNDER_CLIENT while(true) { try @@ -94,6 +102,7 @@ bool DSClientReqHandler::init() RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s()] Device manager Initialized success break loop \n", __FUNCTION__); break; } +#endif /* USE_THUNDER_CLIENT */ RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s:%s] Exiting..\n", __FUNCTION__, __FILE__); return true; } @@ -118,7 +127,9 @@ bool DSClientReqHandler::unInit() hostIf_STBServiceVideoDecoder::closeAllInstances(); hostIf_STBServiceAudioInterface::closeAllInstances(); hostIf_STBServiceSPDIF::closeAllInstances(); +#ifndef USE_THUNDER_CLIENT device::Manager::DeInitialize(); +#endif /* USE_THUNDER_CLIENT */ RDK_LOG(RDK_LOG_TRACE1,LOG_TR69HOSTIF,"[%s:%s] Exiting..\n", __FUNCTION__, __FILE__); return true; } diff --git a/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp b/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp index 26a3f0d36..660e1ba2b 100644 --- a/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp +++ b/src/hostif/handlers/src/hostIf_rbus_Dml_Provider.cpp @@ -49,8 +49,7 @@ extern "C" #include "hostIf_msgHandler.h" #include "waldb.h" //#include "rbus.h" -#include "exception.hpp" -#include "illegalArgumentException.hpp" + #define MAX_NUM_PARAMETERS 2048 diff --git a/src/hostif/include/hostIf_utils.h b/src/hostif/include/hostIf_utils.h index 5420c96c1..e48284f1b 100755 --- a/src/hostif/include/hostIf_utils.h +++ b/src/hostif/include/hostIf_utils.h @@ -284,6 +284,13 @@ bool invokeThunderPluginMethodAndExtractDelimitedStringArrayField(const std::str bool invokeThunderPluginMethodAndExtractScalarStringResult(const std::string& method, const std::string& paramsJson, std::string& value); +/** + * Invoke a Thunder JSON-RPC method and extract the top-level "result" field as a plain bool. + * Use this when the response shape is {"result": true} rather than {"result": {"field": true}}. + */ +bool invokeThunderPluginMethodAndExtractScalarBoolResult(const std::string& method, + const std::string& paramsJson, bool& value); + #endif /* HOSTIF_UTILS_H_*/ diff --git a/src/hostif/parodusClient/pal/mock-parodus/Makefile.am b/src/hostif/parodusClient/pal/mock-parodus/Makefile.am index 1e789bd95..018d3ea0a 100644 --- a/src/hostif/parodusClient/pal/mock-parodus/Makefile.am +++ b/src/hostif/parodusClient/pal/mock-parodus/Makefile.am @@ -71,5 +71,5 @@ parodus_SOURCES = \ parodus_CPPFLAGS = -I../ -I../../../../unittest/stubs/ -I/usr/local/include/wrp-c/ -I../../waldb/ -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/local/include/wdmp-c/ -I/usr/local/include/rbus -I../../../include/ -I../../../../unittest/stubs/ -I../../../handlers/src/ -I/usr/local/include/cjson/ -I/usr/local/include/libparodus/ -I./ -I../../waldb/ -I../../../handlers/include/ -I../../../profiles/Time/ -I../../../profiles/DeviceInfo/ -I../../../profiles/Device -I../../../handlers/include/ -I../../../../unittest/stubs/ds -I../../../profiles/Ethernet/ -I../../../include/ -I../../../profiles/IP/ -I../../../profiles/STBService/ -I/usr/rdk-halif-device_settings/include/ -I/usr/rdkvhal-devicesettings-raspberrypi4/ -DUSE_REMOTE_DEBUGGER -I/usr/remote_debugger/src/ -parodus_LDADD = -llibparodus -lrdkloggers -lwdmp-c -lwrp-c -lmsgpackc -lprocps -ltrower-base64 -lcimplog -lsoup-3.0 -lcjson -lpthread -lglib-2.0 -lnanomsg -lrbus -lIARMBus -lyajl -lds -ldshalcli -ldbus-1 -lsecure_wrapper -lcurl -L../../waldb/.libs/ -l:libwaldb.a -L../../../profiles/Time/.libs/ -l:libhostIfTime.a -ltinyxml2 -lIARMBus +parodus_LDADD = -llibparodus -lrdkloggers -lwdmp-c -lwrp-c -lmsgpackc -lprocps -ltrower-base64 -lcimplog -lsoup-3.0 -lcjson -lpthread -lglib-2.0 -lnanomsg -lrbus -lIARMBus -lyajl -ldbus-1 -lsecure_wrapper -lcurl -L../../waldb/.libs/ -l:libwaldb.a -L../../../profiles/Time/.libs/ -l:libhostIfTime.a -ltinyxml2 -lIARMBus diff --git a/src/hostif/parodusClient/waldb/data-model/data-model-generic.xml b/src/hostif/parodusClient/waldb/data-model/data-model-generic.xml index 76c4e9202..ade87bbc7 100644 --- a/src/hostif/parodusClient/waldb/data-model/data-model-generic.xml +++ b/src/hostif/parodusClient/waldb/data-model/data-model-generic.xml @@ -2012,11 +2012,6 @@ - - - - - @@ -2076,11 +2071,6 @@ - - - - - @@ -2112,11 +2102,6 @@ - - - - - @@ -2128,11 +2113,6 @@ - - - - - @@ -2149,41 +2129,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2230,7 +2185,7 @@ - + @@ -2277,26 +2232,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/hostif/parodusClient/waldb/data-model/data-model-tv.xml b/src/hostif/parodusClient/waldb/data-model/data-model-tv.xml index b5ff30012..876ba3601 100644 --- a/src/hostif/parodusClient/waldb/data-model/data-model-tv.xml +++ b/src/hostif/parodusClient/waldb/data-model/data-model-tv.xml @@ -414,12 +414,6 @@ - - - - - - diff --git a/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp b/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp index aa5f3710d..593591046 100644 --- a/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp +++ b/src/hostif/profiles/DeviceInfo/Device_DeviceInfo.cpp @@ -72,11 +72,13 @@ #include "rbus.h" #include +#ifndef USE_THUNDER_CLIENT #include "dsTypes.h" #include "host.hpp" #include "manager.hpp" #include "dsError.h" #include "audioOutputPort.hpp" +#endif /* USE_THUNDER_CLIENT */ #include "sysMgr.h" #ifdef RDKV_NM @@ -2096,13 +2098,30 @@ int hostIf_DeviceInfo::get_Device_DeviceInfo_X_RDKCENTRAL_COM_BootStatus (HOSTIF */ int hostIf_DeviceInfo::get_Device_DeviceInfo_X_RDKCENTRAL_COM_CPUTemp(HOSTIF_MsgData_t *stMsgData, bool *pChanged) { + /* hostIf framework has no float param type; temperature is rounded to the nearest + * integer degree Celsius (matching the original non-Thunder DS path). */ int cpuTemp = 0; +#ifdef USE_THUNDER_CLIENT + std::string temperatureStr; + if (invokeThunderPluginMethodAndExtractStringField("org.rdk.PowerManager.getThermalState", "", "currentTemperature", temperatureStr)) + { + float dsCpuTemp = 0.0f; + try { dsCpuTemp = std::stof(temperatureStr); } catch (...) {} + cpuTemp = (int)round(dsCpuTemp); + RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s:%s] Current CPU temperature is: %+7.2fC and roundoff CPUTemp : [%d] \n", + __FILE__, __FUNCTION__, dsCpuTemp, cpuTemp); + } + else + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s] getThermalState Thunder call failed\n",__FUNCTION__); + return NOK; + } +#else float dsCpuTemp = device::Host::getInstance().getCPUTemperature(); cpuTemp = (int)round(dsCpuTemp); - RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s:%s] Current CPU temperature is: %+7.2fC and roundoff CPUTemp : [%d] \n", __FILE__, __FUNCTION__, dsCpuTemp, cpuTemp); - +#endif /* USE_THUNDER_CLIENT */ put_int(stMsgData->paramValue, cpuTemp); stMsgData->paramtype = hostIf_IntegerType; return OK; @@ -3716,6 +3735,14 @@ int hostIf_DeviceInfo::set_xRDKCentralComRFC(HOSTIF_MsgData_t * stMsgData) { bool enable = get_boolean(stMsgData->paramValue); RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s] MS12->DAPV2 RFC status:%d\n",__FUNCTION__, enable); +#ifdef USE_THUNDER_CLIENT + bool success = false; + std::string paramsJson = std::string("{\"dolbyVolumeMode\":") + (enable ? "true" : "false") + "}"; + if (!invokeThunderPluginMethodAndExtractBoolField("org.rdk.DisplaySettings.setDolbyVolumeMode", paramsJson, "success", success) || !success) + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s] setDolbyVolumeMode (DAPV2) Thunder call failed\n",__FUNCTION__); + } +#else if(enable) { device::Host::getInstance().getAudioOutputPort("HDMI0").enableMS12Config(dsMS12FEATURE_DAPV2,1); @@ -3724,11 +3751,20 @@ int hostIf_DeviceInfo::set_xRDKCentralComRFC(HOSTIF_MsgData_t * stMsgData) { device::Host::getInstance().getAudioOutputPort("HDMI0").enableMS12Config(dsMS12FEATURE_DAPV2,0); } +#endif } else if (strcasecmp(stMsgData->paramName,MS12_DE_RFC_ENABLE) == 0) { bool enable = get_boolean(stMsgData->paramValue); RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s] MS12->DE RFC status:%d\n",__FUNCTION__, enable); +#ifdef USE_THUNDER_CLIENT + bool success = false; + std::string paramsJson = std::string("{\"dolbyVolumeMode\":") + (enable ? "true" : "false") + "}"; + if (!invokeThunderPluginMethodAndExtractBoolField("org.rdk.DisplaySettings.setDolbyVolumeMode", paramsJson, "success", success) || !success) + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s] setDolbyVolumeMode (DE) Thunder call failed\n",__FUNCTION__); + } +#else if(enable) { device::Host::getInstance().getAudioOutputPort("HDMI0").enableMS12Config(dsMS12FEATURE_DE,1); @@ -3737,6 +3773,7 @@ int hostIf_DeviceInfo::set_xRDKCentralComRFC(HOSTIF_MsgData_t * stMsgData) { device::Host::getInstance().getAudioOutputPort("HDMI0").enableMS12Config(dsMS12FEATURE_DE,0); } +#endif } else if (strcasecmp(stMsgData->paramName,LE_RFC_ENABLE) == 0) { @@ -4376,12 +4413,17 @@ int hostIf_DeviceInfo::set_xRDKCentralComRFCRoamTrigger(HOSTIF_MsgData_t *stMsgD int hostIf_DeviceInfo::set_xRDKCentralComRFCLoudnessEquivalenceEnable(HOSTIF_MsgData_t *stMsgData) { int ret = NOK; - bool enable = false; - dsError_t status = dsERR_GENERAL; if(stMsgData->paramtype == hostIf_BooleanType) { - enable = get_boolean(stMsgData->paramValue); +#ifdef USE_THUNDER_CLIENT + /* enableLEConfig is not supported on RDKe with Thunder client */ + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s] enableLEConfig not supported with Thunder client\n",__FUNCTION__); + ret = NOK; + stMsgData->faultCode = fcInternalError; +#else + bool enable = get_boolean(stMsgData->paramValue); + dsError_t status = dsERR_GENERAL; try { //set the value TRUE/FALSE. @@ -4402,6 +4444,7 @@ int hostIf_DeviceInfo::set_xRDKCentralComRFCLoudnessEquivalenceEnable(HOSTIF_Msg { ret = OK; } +#endif } else { diff --git a/src/hostif/profiles/DeviceInfo/Makefile.am b/src/hostif/profiles/DeviceInfo/Makefile.am index 60ca61f23..3206b2c63 100755 --- a/src/hostif/profiles/DeviceInfo/Makefile.am +++ b/src/hostif/profiles/DeviceInfo/Makefile.am @@ -33,7 +33,13 @@ $(GLIB_CFLAGS) $(G_THREAD_CFLAGS) $(XREMGR_FLAGS) $(MOCAMGR_FLAGS) $(PROCPS_CFLA -I=/usr/include/wdmp-c/ \ -I=/usr/include/rbus/ -AM_LDFLAGS = $(PROCPS_LIBS) -lIARMBus -lyajl -lds -ldshalcli -ldbus-1 -lcurl -lrfcapi -lrbus +AM_LDFLAGS = $(PROCPS_LIBS) -lIARMBus -lyajl -ldbus-1 -lcurl -lrfcapi -lrbus + +if WITH_THUNDER_CLIENT +AM_CXXFLAGS += -DUSE_THUNDER_CLIENT +else +AM_LDFLAGS += -lds -ldshalcli +endif if IS_YOCTO_ENABLED AM_LDFLAGS += -lsecure_wrapper diff --git a/src/hostif/profiles/STBService/Capabilities_Thunder.cpp b/src/hostif/profiles/STBService/Capabilities_Thunder.cpp new file mode 100755 index 000000000..f1fb567c6 --- /dev/null +++ b/src/hostif/profiles/STBService/Capabilities_Thunder.cpp @@ -0,0 +1,460 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2017 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 Capabilities_Thunder.cpp + * @brief Thunder-backed implementation of TR069 STBService Capabilities. + */ + +#include +#include +#include "rdk_debug.h" +#include "hostIf_main.h" +#include "Capabilities.h" + +#define MAX_RESOLUTION_LENGTH 30 + +#define THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS "org.rdk.DisplaySettings.getSupportedVideoCodingFormats" +#define THUNDER_DS_GET_VIDEO_CODEC_INFO "org.rdk.DisplaySettings.getVideoCodecInfo" +#define THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS "org.rdk.DisplaySettings.getSupportedSettopResolutions" + +hostIf_STBServiceCapabilities* hostIf_STBServiceCapabilities::getInstance() +{ + hostIf_STBServiceCapabilities* pRet = NULL; + + if(!pRet) + { + try { + pRet = new hostIf_STBServiceCapabilities(); + } catch(const std::exception& e) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Caught exception \" %s\"\n", __FUNCTION__, e.what()); + } + } + return pRet; +} + + +void hostIf_STBServiceCapabilities::closeInstance(hostIf_STBServiceCapabilities *pDev) +{ + if(pDev) + { + delete pDev; + } +} + +hostIf_STBServiceCapabilities::hostIf_STBServiceCapabilities() +{ +} + +int hostIf_STBServiceCapabilities::handleSetMsg(HOSTIF_MsgData_t *stMsgData) +{ + int ret = NOT_HANDLED; + stMsgData->faultCode = fcAttemptToSetaNonWritableParameter; + return ret; +} + +int hostIf_STBServiceCapabilities::handleGetMsg(HOSTIF_MsgData_t *stMsgData) +{ + int ret = NOT_HANDLED; + const char *path = NULL, *paramName = NULL, *attr = NULL; + int index = 0; + try { + int str_len = strlen(CAPABILITIES_OBJ); + path = stMsgData->paramName; + + RDK_LOG(RDK_LOG_TRACE1,LOG_TR69HOSTIF,"[%s:%s]Entering... \n", __FILE__, __FUNCTION__); + + if(NULL == path) { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s:%d]Failed : Parameter is NULL\n", __FILE__, __FUNCTION__, __LINE__); + stMsgData->faultCode = fcInvalidParameterName; + return ret; + } + + if((strncasecmp(path, CAPABILITIES_OBJ, str_len) != 0)) { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s:%d]Failed : Mismatch parameter path : %s \n", __FILE__, __FUNCTION__, __LINE__, path); + stMsgData->faultCode = fcInvalidParameterName; + return ret; + } + + /* Parse video decoder object.*/ + const char *tmp_ptr = strchr(path+str_len-1,'.'); + if(tmp_ptr == NULL) { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s] Parameter is NULL \n", __FILE__, __FUNCTION__); + stMsgData->faultCode = fcInvalidParameterName; + return ret; + } + + tmp_ptr++; + paramName = tmp_ptr; //Now points to STBService.1.Capabilities.* + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting Capabilities param: %s\n", __FUNCTION__, stMsgData->paramName); + if (strcasecmp(paramName, VIDEO_STANDARDS_STRING) == 0) + { + ret = getVideoStandards(stMsgData); + } + else if(strcasecmp(paramName, PROFILE_NUM_ENTRIES_STRING) == 0) + { + ret = getNumHEVCProfileEntries(stMsgData); + } + else if(matchComponent(stMsgData->paramName, HEVC_PROFILE_OBJ, &attr, index)) + { + //Profile-specific details. One of many profiles. + ret = getHEVCProfileDetails(stMsgData, attr, index); + } + else if(strcasecmp(stMsgData->paramName, HDMI_RESOLUTIONS_STRING) == 0) + { + ret = getSupportedResolutions(stMsgData); + } + else + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s] Parameter \'%s\' is Not Supported \n", __FILE__, __FUNCTION__, paramName); + stMsgData->faultCode = fcInvalidParameterName; + ret = NOK; + } + } + catch (const std::exception& e ) + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s] Exception caught %s \n", __FILE__, __FUNCTION__, e.what()); + stMsgData->faultCode = fcInternalError; + return NOK; + } + RDK_LOG(RDK_LOG_TRACE1,LOG_TR69HOSTIF,"[%s:%s]Exiting... \n", __FILE__, __FUNCTION__); + return ret; +} + +int hostIf_STBServiceCapabilities::getVideoStandards(HOSTIF_MsgData_t *stMsgData) +{ + try { + // Thunder response: { "supportedFormats": ["HEVC", "H264", "MPEG2"], "success": true } + // The field is a string array — use raw response + strstr per codec. + std::string response; + if (!invokeThunderPluginMethod( + THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS, + "{}", + response)) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Failed to fetch supported video coding formats from Thunder\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + const char* resp = response.c_str(); + // Codec strings are set by the plugin as: HEVC, H264, MPEG2 + // dsVIDEO_CODEC_MPEGHPART2 → "HEVC" + // dsVIDEO_CODEC_MPEG4PART10 → "H264" + // dsVIDEO_CODEC_MPEG2 → "MPEG2" + std::string out; + if (strstr(resp, "\"HEVC\"")) + out += "MPEGH-Part2 ([ISO/IEC23008-1]),"; + if (strstr(resp, "\"MPEG2\"")) + out += "MPEG2-Part2 ([ISO/IEC13818-1]),"; + if (strstr(resp, "\"H264\"")) + out += "MPEG4-Part10 ([ISO/IEC14496-10]),"; + + if (out.empty()) + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s] Thunder returned no supported video standards\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + out.pop_back(); /* remove trailing comma */ + strncpy(stMsgData->paramValue, out.c_str(), TR69HOSTIFMGR_MAX_PARAM_LEN - 1); + stMsgData->paramValue[TR69HOSTIFMGR_MAX_PARAM_LEN - 1] = '\0'; + RDK_LOG(RDK_LOG_DEBUG,LOG_TR69HOSTIF,"[%s] : Value: %s \n",__FUNCTION__, stMsgData->paramValue); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + } + catch (const std::exception &e) { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Exception\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + return OK; +} + +struct ThunderHEVCEntry { + std::string profile; // directly from Thunder: "MAIN", "MAIN 10", "MAIN STILL PICTURE" + float level; // raw libds float, e.g. 5.1 for Level 5.1 +}; + +static bool getThunderVideoCodecInfo(unsigned int& numEntries, std::vector& entries) +{ + numEntries = 0; + entries.clear(); + + std::string response; + // Query specifically for MPEGHPart2 — matching the original libds getVideoCodecInfo(dsVIDEO_CODEC_MPEGHPART2). + if (!invokeThunderPluginMethod(THUNDER_DS_GET_VIDEO_CODEC_INFO, "{\"codec\":\"MPEGH-Part2\"}", response)) + { + return false; + } + + cJSON* root = cJSON_Parse(response.c_str()); + if (root == NULL) + { + return false; + } + + cJSON* resultObj = cJSON_GetObjectItem(root, "result"); + if (!cJSON_IsObject(resultObj)) + { + cJSON_Delete(root); + return false; + } + + cJSON* numEntriesObj = cJSON_GetObjectItem(resultObj, "numberOfEntries"); + if (!cJSON_IsNumber(numEntriesObj)) + { + cJSON_Delete(root); + return false; + } + + cJSON* entriesObj = cJSON_GetObjectItem(resultObj, "entries"); + if (!cJSON_IsArray(entriesObj)) + { + cJSON_Delete(root); + return false; + } + + numEntries = numEntriesObj->valueint; + + bool ok = (numEntries > 0); + for (unsigned int i = 0; ok && (i < numEntries); ++i) + { + cJSON* entryObj = cJSON_GetArrayItem(entriesObj, i); + cJSON* profileObj = cJSON_GetObjectItem(entryObj, "profile"); + cJSON* levelObj = cJSON_GetObjectItem(entryObj, "level"); + + if (!cJSON_IsString(profileObj) || (profileObj->valuestring == NULL) || !cJSON_IsNumber(levelObj)) + { + ok = false; + break; + } + + ThunderHEVCEntry entry; + entry.profile = profileObj->valuestring; + entry.level = (float)levelObj->valuedouble; + entries.push_back(std::move(entry)); + } + + cJSON_Delete(root); + return ok; +} + +int hostIf_STBServiceCapabilities::getNumHEVCProfileEntries(HOSTIF_MsgData_t *stMsgData) +{ + try { + unsigned int numEntries = 0; + std::vector entries; + if (!getThunderVideoCodecInfo(numEntries, entries)) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Failed to fetch HEVC codec info from Thunder\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + if(0 == numEntries) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "[%s] Zero profile entries reported.\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + put_int(stMsgData->paramValue, numEntries); + RDK_LOG(RDK_LOG_DEBUG,LOG_TR69HOSTIF,"[%s] : Value: %s \n",__FUNCTION__, stMsgData->paramValue); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + } + catch (const std::exception &e) { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Exception\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + return OK; +} + +static const char* getTR181ResolutionString(const std::string& resolution) +{ + if (resolution == "720p") return "1280x720p/59.94Hz"; + if (resolution == "1080i") return "1920x1080i/59.94Hz"; + if (resolution == "1080p60" || resolution == "1080p") return "1920x1080p/59.94Hz"; + if (resolution == "2160p30") return "3840x2160p/30Hz"; + if (resolution == "2160p60") return "3840x2160p/59.94Hz"; + if (resolution == "480i") return "720x480i/59.94Hz"; + if (resolution == "480p") return "720x480p/59.94Hz"; + if (resolution == "576p50" || resolution == "576p") return "720x576p/50Hz"; + if (resolution == "720p50") return "1280x720p/50Hz"; + if (resolution == "1080p30") return "1920x1080p/30Hz"; + if (resolution == "1080i50" || resolution == "1080i25") return "1920x1080i/50Hz"; + if (resolution == "1080p24") return "1920x1080p/24Hz"; + if (resolution == "1080p50") return "1920x1080p/50Hz"; + if (resolution == "2160p50") return "3840x2160p/50Hz"; + if (resolution == "1080p25") return "1920x1080p/25Hz"; + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "%s: Unhandled resolution: %s. Cannot translate!\n", __FUNCTION__, resolution.c_str()); + return ""; +} + +static unsigned int getMaxHEVCDecodeKBitRate(const ThunderHEVCEntry& entry) +{ + unsigned int kbit_rate = 0; + if ((entry.profile == "MAIN 10") && (5.1f == entry.level)) + { + kbit_rate = 40000; + } + else + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "[%s] Unknown profile (%s) and level (%g) combination!\n",__FUNCTION__, entry.profile.c_str(), entry.level); + } + return kbit_rate; +} + +int hostIf_STBServiceCapabilities::getHEVCProfileDetails(HOSTIF_MsgData_t * stMsgData, const char* attr, unsigned int index) +{ + int bytes_written = 0; + try { + unsigned int numEntries = 0; + std::vector entries; + if (!getThunderVideoCodecInfo(numEntries, entries)) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Failed to fetch HEVC codec info from Thunder\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + if((0 == numEntries) || (0 == index) || (index > numEntries)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "[%s] Could not find profiles matching index %d.\n",__FUNCTION__, index); + stMsgData->faultCode = ((0 == numEntries) ? fcInternalError : fcInvalidParameterName); + return NOK; + } + + const ThunderHEVCEntry& entry = entries[index - 1]; + + if(strcasecmp(attr, PROFILE_NAME_STRING) == 0) + { + bytes_written = snprintf(stMsgData->paramValue, TR69HOSTIFMGR_MAX_PARAM_LEN, "%s", entry.profile.c_str()); + stMsgData->paramValue[bytes_written] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = bytes_written; + } + else if(strcasecmp(attr, PROFILE_LEVEL_STRING) == 0) + { + bytes_written = snprintf(stMsgData->paramValue, TR69HOSTIFMGR_MAX_PARAM_LEN, "L%g", entry.level); + stMsgData->paramValue[bytes_written] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = bytes_written; + } + else if(strcasecmp(attr, PROFILE_MAX_DECODE_CAPABILITY_STRING) == 0) + { + put_int(stMsgData->paramValue, getMaxHEVCDecodeKBitRate(entry)); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + } + else + { + RDK_LOG(RDK_LOG_ERROR,LOG_TR69HOSTIF,"[%s:%s] Parameter \'%s\' is Not Supported \n", __FILE__, __FUNCTION__, attr); + stMsgData->faultCode = fcInvalidParameterName; + } + + } + catch (...) { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Exception\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + return OK; + +} + +int hostIf_STBServiceCapabilities::getSupportedResolutions(HOSTIF_MsgData_t *stMsgData) +{ + try + { + // Thunder returns: { "supportedSettopResolutions": ["720p", "1080p", ...], "success": true } + // Each entry is a short-form name — must be translated to TR-181 "WxHp/FHz" format, + // matching the behaviour of the original libds getSettopSupportedResolutions(). + std::string response; + if (!invokeThunderPluginMethod( + THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS, + "{}", + response)) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Failed to fetch supported settop resolutions from Thunder\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + cJSON* root = cJSON_Parse(response.c_str()); + if (root == NULL) + { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Failed to parse Thunder response\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + // Response may be the full JSON-RPC envelope ({ result: { ... } }) or just the result object. + cJSON* resultObj = cJSON_GetObjectItem(root, "result"); + cJSON* arrayObj = cJSON_IsObject(resultObj) + ? cJSON_GetObjectItem(resultObj, "supportedSettopResolutions") + : cJSON_GetObjectItem(root, "supportedSettopResolutions"); + + if (!cJSON_IsArray(arrayObj)) + { + cJSON_Delete(root); + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] supportedSettopResolutions not found or not an array\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + + memset(stMsgData->paramValue, 0, TR69HOSTIFMGR_MAX_PARAM_LEN); + bool first = true; + const int count = cJSON_GetArraySize(arrayObj); + for (int i = 0; i < count; ++i) + { + cJSON* item = cJSON_GetArrayItem(arrayObj, i); + if (!cJSON_IsString(item) || (item->valuestring == NULL)) + continue; + + const char* tr181 = getTR181ResolutionString(std::string(item->valuestring)); + if (tr181[0] == '\0') + continue; // unknown short name — skip + + if (!first) + strncat(stMsgData->paramValue, ",", TR69HOSTIFMGR_MAX_PARAM_LEN - strlen(stMsgData->paramValue) - 1); + strncat(stMsgData->paramValue, tr181, TR69HOSTIFMGR_MAX_PARAM_LEN - strlen(stMsgData->paramValue) - 1); + first = false; + RDK_LOG(RDK_LOG_DEBUG,LOG_TR69HOSTIF,"[%s] : resolution: %s -> %s\n",__FUNCTION__, item->valuestring, tr181); + } + + cJSON_Delete(root); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + RDK_LOG(RDK_LOG_DEBUG,LOG_TR69HOSTIF,"[%s] : Value: %s \n",__FUNCTION__, stMsgData->paramValue); + } + catch (...) { + RDK_LOG(RDK_LOG_WARN,LOG_TR69HOSTIF,"[%s] Exception\n",__FUNCTION__); + stMsgData->faultCode = fcInternalError; + return NOK; + } + return OK; + +} diff --git a/src/hostif/profiles/STBService/Components_AudioOutput.h b/src/hostif/profiles/STBService/Components_AudioOutput.h index c248a1a32..ed55d2cce 100755 --- a/src/hostif/profiles/STBService/Components_AudioOutput.h +++ b/src/hostif/profiles/STBService/Components_AudioOutput.h @@ -85,6 +85,7 @@ #define DEVSET_COMP_AUDIOOUTPUT_HPP__ #include +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "videoDevice.hpp" #include "videoDFC.hpp" @@ -92,6 +93,7 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include @@ -99,7 +101,9 @@ #include "hostIf_tr69ReqHandler.h" #include "hostIf_updateHandler.h" #include "hostIf_main.h" +#ifndef USE_THUNDER_CLIENT #include "audioOutputPort.hpp" +#endif /* USE_THUNDER_CLIENT */ #ifndef PARAM_LEN #define PARAM_LEN TR69HOSTIFMGR_MAX_PARAM_LEN @@ -111,12 +115,22 @@ */ class hostIf_STBServiceAudioInterface { +#ifdef USE_THUNDER_CLIENT + static GHashTable *ifHash; /* dev_id -> hostIf_STBServiceAudioInterface* */ + hostIf_STBServiceAudioInterface(int dev_id, const std::string& portName); +#else static GHashTable *ifHash; hostIf_STBServiceAudioInterface(int dev_id, device::AudioOutputPort& port); +#endif ~hostIf_STBServiceAudioInterface() {}; static GMutex m_mutex; int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; + static void buildPortNameHash(); +#else device::AudioOutputPort& aPort; +#endif char backupStatus[_BUF_LEN_16]; bool backupCancelMute; diff --git a/src/hostif/profiles/STBService/Components_AudioOutput_Thunder.cpp b/src/hostif/profiles/STBService/Components_AudioOutput_Thunder.cpp new file mode 100644 index 000000000..aa70f564d --- /dev/null +++ b/src/hostif/profiles/STBService/Components_AudioOutput_Thunder.cpp @@ -0,0 +1,479 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_AudioOutput_Thunder.cpp + * @brief Thunder-backed implementation of TR069 Components AudioOutput. + */ + +#include +#include "Components_AudioOutput.h" + +#define DEV_NAME "AudioOutput" +#define BASE_NAME "Device.Services.STBService.1.Components.AudioOutput" +#define UPDATE_FORMAT_STRING "%s.%d.%s" + +#define STATUS_STRING "Status" +#define ENABLED_STRING "Enabled" +#define ENABLE_STRING "Enable" +#define CANCELMUTE_STRING "CancelMute" +#define AUDIOFORMAT_STRING "AudioFormat" +#define NAME_STRING "Name" +#define AUDIOLEVEL_STRING "AudioLevel" +#define COMCAST_AUDIOOPTIMALLEVEL_STRING "X_COMCAST-COM_AudioOptimalLevel" +#define COMCAST_MINAUDIODB_STRING "X_COMCAST-COM_MinAudioDB" +#define COMCAST_MAXAUDIODB_STRING "X_COMCAST-COM_MaxAudioDB" +#define COMCAST_AUDIODB_STRING "X_COMCAST-COM_AudioDB" +#define COMCAST_AUDIOSTEREOMODE_STRING "X_COMCAST-COM_AudioStereoMode" +#define COMCAST_AUDIOLOOPTHRU_STRING "X_COMCAST-COM_AudioLoopThru" +#define COMCAST_AUDIOENCODING_STRING "X_COMCAST-COM_AudioEncoding" +#define COMCAST_AUDIOCOMPRESSION_STRING "X_COMCAST-COM_AudioCompression" +#define COMCAST_AUDIOGAIN_STRING "X_COMCAST-COM_AudioGain" +#define COMCAST_DIALOGENHANCEMENT_STRING "X_COMCAST-COM_DialogEnhancement" + +#define THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS "org.rdk.DisplaySettings.getSupportedAudioPorts" +#define THUNDER_DS_GET_ENABLE_AUDIO_PORT "org.rdk.DisplaySettings.getEnableAudioPort" +#define THUNDER_DS_GET_MUTED "org.rdk.DisplaySettings.getMuted" +#define THUNDER_DS_GET_VOLUME_LEVEL "org.rdk.DisplaySettings.getVolumeLevel" +#define THUNDER_DS_GET_AUDIO_ENCODING "org.rdk.DisplaySettings.getAudioEncoding" +#define THUNDER_DS_GET_AUDIO_FORMAT "org.rdk.DisplaySettings.getAudioFormat" +#define THUNDER_DS_GET_SOUND_MODE "org.rdk.DisplaySettings.getSoundMode" +#define THUNDER_DS_GET_MS12_AUDIO_COMPRESSION "org.rdk.DisplaySettings.getMS12AudioCompression" +#define THUNDER_DS_GET_AUDIO_DELAY "org.rdk.DisplaySettings.getAudioDelay" +#define THUNDER_DS_GET_DIALOG_ENHANCEMENT "org.rdk.DisplaySettings.getDialogEnhancement" + +GHashTable * hostIf_STBServiceAudioInterface::ifHash = NULL; +GMutex hostIf_STBServiceAudioInterface::m_mutex; + +void hostIf_STBServiceAudioInterface::buildPortNameHash() +{ + if (ifHash) + closeAllInstances(); + + ifHash = g_hash_table_new(NULL, NULL); + + std::string delimitedPorts; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, "[%s] Calling Thunder API: %s\n", __FUNCTION__, THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS); + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS, "{}", "supportedAudioPorts", ",", delimitedPorts)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, + "[%s:%d] Failed to get supported audio ports\n", __FUNCTION__, __LINE__); + return; + } + + int devId = 1; + std::istringstream ss(delimitedPorts); + std::string portName; + while (std::getline(ss, portName, ',')) + { + if (!portName.empty()) + { + hostIf_STBServiceAudioInterface *pInst = new hostIf_STBServiceAudioInterface(devId, portName); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)devId, pInst); + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, + "[%s:%d] dev_id=%d portName=%s\n", __FUNCTION__, __LINE__, devId, portName.c_str()); + devId++; + } + } +} + +hostIf_STBServiceAudioInterface* hostIf_STBServiceAudioInterface::getInstance(int dev_id) +{ + if (!ifHash) + buildPortNameHash(); + + hostIf_STBServiceAudioInterface* pRet = + (hostIf_STBServiceAudioInterface*)g_hash_table_lookup(ifHash, (gpointer)(intptr_t)dev_id); + + if (!pRet) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, + "[%s:%s:%d]: No instance for dev_id=%d\n", __FILE__, __FUNCTION__, __LINE__, dev_id); + } + return pRet; +} + +GList* hostIf_STBServiceAudioInterface::getAllInstances() +{ + if(ifHash) + return g_hash_table_get_keys(ifHash); + return NULL; +} + +void hostIf_STBServiceAudioInterface::closeInstance(hostIf_STBServiceAudioInterface *pDev) +{ + if(pDev) + { + if (ifHash) + g_hash_table_remove(ifHash, (gconstpointer)(intptr_t)pDev->dev_id); + delete pDev; + } +} + +void hostIf_STBServiceAudioInterface::closeAllInstances() +{ + if(ifHash) + { + GList* tmp_list = g_hash_table_get_values(ifHash); + GList* current = tmp_list; + + while(current) + { + hostIf_STBServiceAudioInterface* pDev = (hostIf_STBServiceAudioInterface*)current->data; + current = current->next; + delete pDev; + } + + g_list_free(tmp_list); + g_hash_table_destroy(ifHash); + ifHash = NULL; + } +} + +void hostIf_STBServiceAudioInterface::getLock() +{ + g_mutex_init(&hostIf_STBServiceAudioInterface::m_mutex); + g_mutex_lock(&hostIf_STBServiceAudioInterface::m_mutex); +} + +void hostIf_STBServiceAudioInterface::releaseLock() +{ + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s:%d] Unlocking mutex...\n", __FUNCTION__, __LINE__); + g_mutex_unlock(&hostIf_STBServiceAudioInterface::m_mutex); +} + +hostIf_STBServiceAudioInterface::hostIf_STBServiceAudioInterface(int devid, const std::string& portName) + : dev_id(devid), m_portName(portName) +{ + strncpy(backupStatus, " ", sizeof(backupStatus)); + backupCancelMute = false; + strncpy(backupAudioStereoMode, " ", sizeof(backupAudioStereoMode)); + backupAudioLevel = 0; + strncpy(backupAudioDB, " ", sizeof(backupAudioDB)); + strncpy(backupAudioLoopThru, " ", sizeof(backupAudioLoopThru)); + backupAudioCompression = 0; + strncpy(backupAudioEncoding, " ", sizeof(backupAudioEncoding)); + strncpy(backupAudioGain, " ", sizeof(backupAudioGain)); + strncpy(backupMinAudioDB, " ", sizeof(backupMinAudioDB)); + strncpy(backupMaxAudioDB, " ", sizeof(backupMaxAudioDB)); + strncpy(backupAudioOptimalLevel, " ", sizeof(backupAudioOptimalLevel)); + backupDialogEnhancement = 0; + + bCalledStatus = false; + bCalledCancelMute = false; + bCalledAudioStereoMode = false; + bCalledAudioLevel = false; + bCalledAudioDB = false; + bCalledAudioLoopThru = false; + bCalledAudioCompression = false; + bCalledAudioEncoding = false; + bCalledAudioGain = false; + bCalledMinAudioDB = false; + bCalledMaxAudioDB = false; + bCalledAudioOptimalLevel = false; + bCalledDialogEnhancement = false; +} + +int hostIf_STBServiceAudioInterface::getNumberOfInstances(HOSTIF_MsgData_t *stMsgData) +{ + if (!ifHash) + buildPortNameHash(); + + put_int(stMsgData->paramValue, g_hash_table_size(ifHash)); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + return OK; +} + +int hostIf_STBServiceAudioInterface::handleSetMsg(const char *pSetting, HOSTIF_MsgData_t *stMsgData) +{ + (void)pSetting; (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceAudioInterface::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + int ret = NOT_HANDLED; + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting AudioOutput param: %s\n", __FUNCTION__, paramName); + if (strcasecmp(paramName, STATUS_STRING) == 0) + ret = getStatus(stMsgData); + else if (strcasecmp(paramName, ENABLE_STRING) == 0) + ret = getEnable(stMsgData); + else if (strcasecmp(paramName, CANCELMUTE_STRING) == 0) + ret = getCancelMute(stMsgData); + else if (strcasecmp(paramName, AUDIOFORMAT_STRING) == 0) + ret = getX_COMCAST_COM_AudioFormat(stMsgData); + else if (strcasecmp(paramName, NAME_STRING) == 0) + ret = getName(stMsgData); + else if (strcasecmp(paramName, AUDIOLEVEL_STRING) == 0) + ret = getAudioLevel(stMsgData); + else if (strcasecmp(paramName, COMCAST_AUDIOOPTIMALLEVEL_STRING) == 0) + ret = getX_COMCAST_COM_AudioOptimalLevel(stMsgData); + else if (strcasecmp(paramName, COMCAST_AUDIOSTEREOMODE_STRING) == 0) + ret = getX_COMCAST_COM_AudioStereoMode(stMsgData); + else if (strcasecmp(paramName, COMCAST_AUDIOENCODING_STRING) == 0) + ret = getX_COMCAST_COM_AudioEncoding(stMsgData); + else if (strcasecmp(paramName, COMCAST_AUDIOCOMPRESSION_STRING) == 0) + ret = getX_COMCAST_COM_AudioCompression(stMsgData); + return ret; +} + +void hostIf_STBServiceAudioInterface::doUpdates(updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; + bool bChanged; + char tmp_buff[PARAM_LEN]; + +#define DO_UPDATE(fn, paramStr) \ + memset(&msgData, 0, sizeof(msgData)); memset(tmp_buff, 0, PARAM_LEN); \ + bChanged = false; msgData.instanceNum = dev_id; \ + fn(&msgData, &bChanged); \ + if (bChanged) { \ + snprintf(tmp_buff, PARAM_LEN, UPDATE_FORMAT_STRING, BASE_NAME, dev_id, paramStr); \ + if (mUpdateCallback) mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED, tmp_buff, msgData.paramValue, msgData.paramtype); \ + } + + DO_UPDATE(getCancelMute, CANCELMUTE_STRING) + DO_UPDATE(getX_COMCAST_COM_AudioEncoding, COMCAST_AUDIOENCODING_STRING) + DO_UPDATE(getAudioLevel, AUDIOLEVEL_STRING) + DO_UPDATE(getX_COMCAST_COM_AudioOptimalLevel, COMCAST_AUDIOOPTIMALLEVEL_STRING) + DO_UPDATE(getX_COMCAST_COM_AudioStereoMode, COMCAST_AUDIOSTEREOMODE_STRING) + DO_UPDATE(getX_COMCAST_COM_AudioCompression, COMCAST_AUDIOCOMPRESSION_STRING) + +#undef DO_UPDATE +} + +/* ---- helpers ---- */ + +static std::string portParam(const std::string& portName) +{ + return std::string("{\"audioPort\":\"") + portName + "\"}"; +} + +/* ---- getters ---- */ + +int hostIf_STBServiceAudioInterface::getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool enabled = false; + if (!invokeThunderPluginMethodAndExtractBoolField( + THUNDER_DS_GET_ENABLE_AUDIO_PORT, portParam(m_portName), "enable", enabled)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getEnableAudioPort failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + + const char *status = "Disabled"; + if (enabled) + { + bool muted = false; + if (!invokeThunderPluginMethodAndExtractBoolField(THUNDER_DS_GET_MUTED, portParam(m_portName), "muted", muted)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getMuted failed for %s, assuming not muted\n", + __FUNCTION__, m_portName.c_str()); + } + status = muted ? "Muted" : "Enabled"; + } + + strncpy(stMsgData->paramValue, status, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(status); + + if (bCalledStatus && pChanged && strcmp(backupStatus, stMsgData->paramValue)) + *pChanged = true; + bCalledStatus = true; + strncpy(backupStatus, stMsgData->paramValue, sizeof(backupStatus) - 1); + backupStatus[sizeof(backupStatus) - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceAudioInterface::getEnable(HOSTIF_MsgData_t *stMsgData) +{ + put_boolean(stMsgData->paramValue, true); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + return OK; +} + +int hostIf_STBServiceAudioInterface::getName(HOSTIF_MsgData_t *stMsgData) +{ + snprintf(stMsgData->paramValue, PARAM_LEN, "AudioOutputPort%s%d", m_portName.c_str(), dev_id); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; +} + +int hostIf_STBServiceAudioInterface::getCancelMute(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool muted = false; + if (!invokeThunderPluginMethodAndExtractBoolField( + THUNDER_DS_GET_MUTED, portParam(m_portName), "muted", muted)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getMuted failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + put_boolean(stMsgData->paramValue, muted); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + if (bCalledCancelMute && pChanged && (backupCancelMute != muted)) + *pChanged = true; + bCalledCancelMute = true; + backupCancelMute = muted; + return OK; +} + +int hostIf_STBServiceAudioInterface::getAudioLevel(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + int volumeLevel = 0; + if (!invokeThunderPluginMethodAndExtractNumberField( + THUNDER_DS_GET_VOLUME_LEVEL, portParam(m_portName), "volumeLevel", volumeLevel)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getVolumeLevel failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + put_int(stMsgData->paramValue, volumeLevel); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + if (bCalledAudioLevel && pChanged && (backupAudioLevel != (unsigned)volumeLevel)) + *pChanged = true; + bCalledAudioLevel = true; + backupAudioLevel = (unsigned)volumeLevel; + return OK; +} + +int hostIf_STBServiceAudioInterface::getX_COMCAST_COM_AudioEncoding(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string encoding; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_AUDIO_ENCODING, portParam(m_portName), "encoding", encoding)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getAudioEncoding failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + /* Map Thunder UPPERCASE encoding to exact TR-135 legacy strings. + * Legacy dsAUDIO_ENC_* values: NONE->"None", DISPLAY->"Display", + * PCM->"PCM", AC3->"AC3", EAC3->"EAC3". Title-case normalization + * is wrong for multi-char acronyms (AC3 -> Ac3, PCM -> Pcm). */ + const char *mapped = encoding.c_str(); /* default: pass through unknown values */ + if (encoding == "NONE") mapped = "None"; + else if (encoding == "DISPLAY") mapped = "Display"; + else if (encoding == "PCM") mapped = "PCM"; + else if (encoding == "AC3") mapped = "AC3"; + else if (encoding == "EAC3") mapped = "EAC3"; + strncpy(stMsgData->paramValue, mapped, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledAudioEncoding && pChanged && strcmp(backupAudioEncoding, stMsgData->paramValue)) + *pChanged = true; + bCalledAudioEncoding = true; + strncpy(backupAudioEncoding, stMsgData->paramValue, _BUF_LEN_16); + backupAudioEncoding[_BUF_LEN_16 - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceAudioInterface::getX_COMCAST_COM_AudioFormat(HOSTIF_MsgData_t *stMsgData) +{ + std::string encoding; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_AUDIO_ENCODING, portParam(m_portName), "encoding", encoding)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getAudioEncoding failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + /* Map Thunder encoding string to TR-181 AudioFormat — mirrors libds dsAudioEncoding_t switch: + * NONE -> "None" + * DISPLAY -> "Other" (platform-selected digital format) + * PCM -> "PCM" + * AC3 -> "AC3" + * EAC3 -> "EAC3" + */ + const char *fmt = "Other"; + if (encoding == "NONE") fmt = "None"; + else if (encoding == "DISPLAY") fmt = "Other"; + else if (encoding == "PCM") fmt = "PCM"; + else if (encoding == "AC3") fmt = "AC3"; + else if (encoding == "EAC3") fmt = "EAC3"; + + strncpy(stMsgData->paramValue, fmt, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(fmt); + return OK; +} + +int hostIf_STBServiceAudioInterface::getX_COMCAST_COM_AudioStereoMode(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string mode; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_SOUND_MODE, portParam(m_portName), "soundMode", mode)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getSoundMode failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + strncpy(stMsgData->paramValue, mode.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledAudioStereoMode && pChanged && strcmp(backupAudioStereoMode, stMsgData->paramValue)) + *pChanged = true; + bCalledAudioStereoMode = true; + strncpy(backupAudioStereoMode, stMsgData->paramValue, sizeof(backupAudioStereoMode) - 1); + backupAudioStereoMode[sizeof(backupAudioStereoMode) - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceAudioInterface::getX_COMCAST_COM_AudioCompression(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + int compression = 0; + if (!invokeThunderPluginMethodAndExtractNumberField( + THUNDER_DS_GET_MS12_AUDIO_COMPRESSION, portParam(m_portName), "compressionlevel", compression)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getAudioCompression failed for %s\n", + __FUNCTION__, m_portName.c_str()); + return NOK; + } + put_int(stMsgData->paramValue, compression); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + if (bCalledAudioCompression && pChanged && (backupAudioCompression != (unsigned)compression)) + *pChanged = true; + bCalledAudioCompression = true; + backupAudioCompression = (unsigned)compression; + return OK; +} + +// TODO: No Thunder API. Update after Operations team confirmation. +int hostIf_STBServiceAudioInterface::getX_COMCAST_COM_AudioOptimalLevel(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + (void)pChanged; + snprintf(stMsgData->paramValue, PARAM_LEN, "0.000000"); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; +} + + diff --git a/src/hostif/profiles/STBService/Components_DisplayDevice.h b/src/hostif/profiles/STBService/Components_DisplayDevice.h index 853e1a936..8b3f0ae32 100644 --- a/src/hostif/profiles/STBService/Components_DisplayDevice.h +++ b/src/hostif/profiles/STBService/Components_DisplayDevice.h @@ -58,6 +58,7 @@ #ifndef DEVSET_COMP_DISPLAYDEVICE_HPP_ #define DEVSET_COMP_DISPLAYDEVICE_HPP_ +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "dsTypes.h" #include "videoOutputPortType.hpp" @@ -67,13 +68,16 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include "stdlib.h" #include "hostIf_tr69ReqHandler.h" #include "hostIf_updateHandler.h" #include "hostIf_utils.h" +#ifndef USE_THUNDER_CLIENT #include "videoOutputPort.hpp" +#endif /* USE_THUNDER_CLIENT */ #ifndef PARAM_LEN #define PARAM_LEN TR69HOSTIFMGR_MAX_PARAM_LEN @@ -90,7 +94,11 @@ class hostIf_STBServiceDisplayDevice { int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; +#else device::VideoOutputPort& vPort; +#endif char backupDisplayDeviceStatus[_BUF_LEN_16]; char backupEDID[_BUF_LEN_256]; @@ -105,13 +113,19 @@ class hostIf_STBServiceDisplayDevice bool bCalledPreferredResolution; int getStatus(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#ifndef USE_THUNDER_CLIENT int getEDID_BYTES(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#endif int getX_COMCAST_COM_EDID(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); int getSupportedResolutions(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); int getPreferredResolution(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); public: +#ifdef USE_THUNDER_CLIENT + hostIf_STBServiceDisplayDevice(int devId, const std::string& portName); +#else hostIf_STBServiceDisplayDevice(int devId, device::VideoOutputPort& port); +#endif void doUpdates(const char *baseName, updateCallback mUpdateCallback); int handleSetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData); int handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData); diff --git a/src/hostif/profiles/STBService/Components_DisplayDevice_Thunder.cpp b/src/hostif/profiles/STBService/Components_DisplayDevice_Thunder.cpp new file mode 100644 index 000000000..142436aa2 --- /dev/null +++ b/src/hostif/profiles/STBService/Components_DisplayDevice_Thunder.cpp @@ -0,0 +1,291 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_DisplayDevice_Thunder.cpp + * @brief Thunder-backed implementation of TR069 Components DisplayDevice. + */ + +#include +#include "Components_DisplayDevice.h" + +#define STATUS_STRING "Status" +#define EEDID_STRING "EEDID" +#define COMCAST_EDID_STRING "X_COMCAST-COM_EDID" +#define EDID_BYTES_STRING "EDID_BYTES" +#define SUPPORTED_RES_STRING "SupportedResolutions" +#define PREF_RES_STRING "PreferredResolution" + +#define THUNDER_DI_CONNECTED "DisplayInfo.1.connected" +#define THUNDER_DS_READ_EDID "org.rdk.DisplaySettings.readEDID" +#define THUNDER_DS_GET_SUPPORTED_RESOLUTIONS "org.rdk.DisplaySettings.getSupportedResolutions" +#define THUNDER_DS_GET_DEFAULT_RESOLUTION "org.rdk.DisplaySettings.getDefaultResolution" + +hostIf_STBServiceDisplayDevice::hostIf_STBServiceDisplayDevice(int devId, const std::string& portName) + : dev_id(devId), m_portName(portName) +{ + strncpy(backupDisplayDeviceStatus, " ", sizeof(backupDisplayDeviceStatus)); + strncpy(backupEDID, " ", sizeof(backupEDID)); + strncpy(backupEDIDBytes, " ", sizeof(backupEDIDBytes)); + strncpy(backupSupportedResolution, " ", sizeof(backupSupportedResolution)); + strncpy(backupPreferredResolution, " ", sizeof(backupPreferredResolution)); + bCalledDisplayDeviceStatus = false; + bCalledEDID = false; + bCalledEDIDBytes = false; + bCalledSupportedResolution = false; + bCalledPreferredResolution = false; +} + +int hostIf_STBServiceDisplayDevice::handleSetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + (void)paramName; (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceDisplayDevice::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting DisplayDevice param: %s\n", __FUNCTION__, paramName); + if (strcasecmp(paramName, STATUS_STRING) == 0) + return getStatus(stMsgData); + if (strcasecmp(paramName, SUPPORTED_RES_STRING) == 0) + return getSupportedResolutions(stMsgData); + if (strcasecmp(paramName, PREF_RES_STRING) == 0) + return getPreferredResolution(stMsgData); + if (strcasecmp(paramName, EEDID_STRING) == 0) + return getX_COMCAST_COM_EDID(stMsgData); /* parsed EDID format, matching libds */ + if (strcasecmp(paramName, COMCAST_EDID_STRING) == 0) + return getX_COMCAST_COM_EDID(stMsgData); + return NOT_HANDLED; +} + +void hostIf_STBServiceDisplayDevice::doUpdates(const char *baseName, updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; + bool bChanged; + char tmp_buff[PARAM_LEN]; + memset(&msgData, 0, sizeof(msgData)); msgData.instanceNum = dev_id; + bChanged = false; + getStatus(&msgData, &bChanged); + if (bChanged) { + snprintf(tmp_buff, PARAM_LEN, "%s%s", baseName, STATUS_STRING); + if (mUpdateCallback) + mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED, tmp_buff, msgData.paramValue, msgData.paramtype); + } +} + +int hostIf_STBServiceDisplayDevice::getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool connected = false; + if (!invokeThunderPluginMethodAndExtractBoolField(THUNDER_DI_CONNECTED, "{}", "isconnected", connected)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] DisplayInfo.1.connected failed, returning Absent\n", __FUNCTION__); + // On failure, default to Absent + connected = false; + } + const char *status = connected ? "Present" : "Absent"; + strncpy(stMsgData->paramValue, status, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(status); + if (bCalledDisplayDeviceStatus && pChanged && strcmp(backupDisplayDeviceStatus, stMsgData->paramValue)) + *pChanged = true; + bCalledDisplayDeviceStatus = true; + strncpy(backupDisplayDeviceStatus, stMsgData->paramValue, sizeof(backupDisplayDeviceStatus) - 1); + backupDisplayDeviceStatus[sizeof(backupDisplayDeviceStatus) - 1] = '\0'; + return OK; +} + +/* Convert DisplaySettings resolution code (e.g. "2160p60") to the full TR-069 format + * (e.g. "3840x2160p/59.94Hz"), matching the format libds produced. */ +static const char *resolveResolutionCode(const char *code, char *buf, size_t bufLen) +{ + static const struct { const char *code; const char *full; } kTable[] = { + { "480p", "720x480p/59.94Hz" }, + { "576i", "720x576i/50Hz" }, + { "576p", "720x576p/50Hz" }, + { "720p50", "1280x720p/50Hz" }, + { "720p", "1280x720p/59.94Hz" }, + { "1080i50", "1920x1080i/50Hz" }, + { "1080i", "1920x1080i/59.94Hz" }, + { "1080p24", "1920x1080p/23.98Hz" }, + { "1080p25", "1920x1080p/25Hz" }, + { "1080p30", "1920x1080p/30Hz" }, + { "1080p50", "1920x1080p/50Hz" }, + { "1080p60", "1920x1080p/59.94Hz" }, + { "2160p24", "3840x2160p/23.98Hz" }, + { "2160p25", "3840x2160p/25Hz" }, + { "2160p30", "3840x2160p/30Hz" }, + { "2160p50", "3840x2160p/50Hz" }, + { "2160p60", "3840x2160p/59.94Hz" }, + { NULL, NULL } + }; + for (int i = 0; kTable[i].code; i++) + { + if (strcmp(code, kTable[i].code) == 0) + { + snprintf(buf, bufLen, "%s", kTable[i].full); + return buf; + } + } + snprintf(buf, bufLen, "%s", code); /* unknown code — return as-is */ + return buf; +} + +int hostIf_STBServiceDisplayDevice::getSupportedResolutions(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + const std::string params = std::string("{\"videoDisplay\":\"") + m_portName + "\"}"; + std::string resolutionsCsv; + + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_RESOLUTIONS, + params, "supportedResolutions", ",", resolutionsCsv)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, + "[%s] Thunder %s failed (port=%s)\n", + __FUNCTION__, THUNDER_DS_GET_SUPPORTED_RESOLUTIONS, m_portName.c_str()); + return NOK; + } + + /* Convert each raw plugin code to the full format (e.g. "720p" -> "1280x720p/59.94Hz") */ + std::string formatted; + { + std::istringstream ss(resolutionsCsv); + std::string token; + char fmtBuf[64]; + while (std::getline(ss, token, ',')) + { + if (token.empty()) continue; + if (!formatted.empty()) formatted += ','; + formatted += resolveResolutionCode(token.c_str(), fmtBuf, sizeof(fmtBuf)); + } + } + + strncpy(stMsgData->paramValue, formatted.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledSupportedResolution && pChanged && strcmp(backupSupportedResolution, stMsgData->paramValue)) + *pChanged = true; + bCalledSupportedResolution = true; + strncpy(backupSupportedResolution, stMsgData->paramValue, sizeof(backupSupportedResolution) - 1); + backupSupportedResolution[sizeof(backupSupportedResolution) - 1] = '\0'; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, + "[%s] SupportedResolutions: %s (port=%s)\n", + __FUNCTION__, stMsgData->paramValue, m_portName.c_str()); + return OK; +} + +int hostIf_STBServiceDisplayDevice::getPreferredResolution(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string resolution; + + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_DEFAULT_RESOLUTION, + "{}", "defaultResolution", resolution)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, + "[%s] Thunder %s failed\n", + __FUNCTION__, THUNDER_DS_GET_DEFAULT_RESOLUTION); + return NOK; + } + + /* Convert raw plugin code to full format (e.g. "720p" -> "1280x720p/59.94Hz") */ + char fmtBuf[64]; + resolveResolutionCode(resolution.c_str(), fmtBuf, sizeof(fmtBuf)); + + strncpy(stMsgData->paramValue, fmtBuf, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledPreferredResolution && pChanged && strcmp(backupPreferredResolution, stMsgData->paramValue)) + *pChanged = true; + bCalledPreferredResolution = true; + strncpy(backupPreferredResolution, stMsgData->paramValue, sizeof(backupPreferredResolution) - 1); + backupPreferredResolution[sizeof(backupPreferredResolution) - 1] = '\0'; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, + "[%s] PreferredResolution: %s\n", + __FUNCTION__, stMsgData->paramValue); + return OK; +} + +int hostIf_STBServiceDisplayDevice::getX_COMCAST_COM_EDID(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + static const char kB64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /* readEDID returns "EDID":"" when display is not connected — treat empty + * EDID as not-connected, matching the original libds isDisplayConnected() check. */ + std::string edidBase64; + if (!invokeThunderPluginMethodAndExtractStringField(THUNDER_DS_READ_EDID, "{}", "EDID", edidBase64)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder readEDID failed, treating as not connected\n", __FUNCTION__); + } + + if (edidBase64.empty()) { + memset(stMsgData->paramValue, '\0', sizeof(stMsgData->paramValue)); + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, "[%s] Display not connected or EDID unavailable\n", __FUNCTION__); + } else { + /* Base64 decode */ + std::string edid; + int val = 0, bits = -8; + for (unsigned char c : edidBase64) { + if (c == '=') break; + const char *p = strchr(kB64Table, (char)c); + if (!p) continue; + val = (val << 6) + (int)(p - kB64Table); + bits += 6; + if (bits >= 0) { + edid += (char)((val >> bits) & 0xFF); + bits -= 8; + } + } + + if (edid.size() < 18) { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] EDID too short (%zu bytes)\n", + __FUNCTION__, edid.size()); + return NOK; + } + + /* EDID byte layout (standard): + * [10-11] productCode (little-endian uint16) + * [12-15] serialNumber (little-endian uint32) + * [16] manufactureWeek + * [17] manufactureYear offset (+1990) */ + int productCode = (unsigned char)edid[10] | ((unsigned char)edid[11] << 8); + int serialNumber = (unsigned char)edid[12] | ((unsigned char)edid[13] << 8) + | ((unsigned char)edid[14] << 16) | ((unsigned char)edid[15] << 24); + int manufactureWeek = (unsigned char)edid[16]; + int manufactureYear = (unsigned char)edid[17] + 1990; + + snprintf(stMsgData->paramValue, PARAM_LEN, + "pcode=0x%x,pserial=0x%x,year=%d,week=%d", + productCode, serialNumber, manufactureYear, manufactureWeek); + } + + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledEDID && pChanged && strcmp(backupEDID, stMsgData->paramValue)) + *pChanged = true; + bCalledEDID = true; + strncpy(backupEDID, stMsgData->paramValue, sizeof(backupEDID) - 1); + backupEDID[sizeof(backupEDID) - 1] = '\0'; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, + "[%s] X_COMCAST_COM_EDID: %s\n", __FUNCTION__, stMsgData->paramValue); + return OK; +} diff --git a/src/hostif/profiles/STBService/Components_HDMI.h b/src/hostif/profiles/STBService/Components_HDMI.h index 38b054c84..93539604c 100755 --- a/src/hostif/profiles/STBService/Components_HDMI.h +++ b/src/hostif/profiles/STBService/Components_HDMI.h @@ -53,6 +53,7 @@ #ifndef DEVSET_COMP_HDMI_HPP_ #define DEVSET_COMP_HDMI_HPP_ +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "videoResolution.hpp" #include "dsTypes.h" @@ -63,6 +64,7 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include "stdlib.h" @@ -71,7 +73,9 @@ #include "hostIf_utils.h" #include "Components_DisplayDevice.h" +#ifndef USE_THUNDER_CLIENT #include "videoOutputPort.hpp" +#endif /* USE_THUNDER_CLIENT */ #define HDMI_RESOLUTION_MODE_AUTO "Auto" #define HDMI_RESOLUTION_MODE_MANUAL "Manual" @@ -87,12 +91,22 @@ */ class hostIf_STBServiceHDMI { +#ifdef USE_THUNDER_CLIENT + static GHashTable *ifHash; /* dev_id -> hostIf_STBServiceHDMI* */ + hostIf_STBServiceHDMI(int devid, const std::string& portName); +#else static GHashTable *ifHash; hostIf_STBServiceHDMI(int devid, device::VideoOutputPort& port); +#endif ~hostIf_STBServiceHDMI(); static GMutex m_mutex; int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; + static void buildPortNameHash(); +#else device::VideoOutputPort& vPort; +#endif hostIf_STBServiceDisplayDevice *displayDevice; static char dsHDMIResolutionMode[10]; @@ -107,13 +121,19 @@ class hostIf_STBServiceHDMI bool bCalledName; private: +#ifndef USE_THUNDER_CLIENT int setResolution(const HOSTIF_MsgData_t *stMsgData); +#endif int getResolutionValue(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#ifndef USE_THUNDER_CLIENT int setEnableVideoPort(const HOSTIF_MsgData_t *stMsgData); +#endif int getEnable(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); int getStatus(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); int getName(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#ifndef USE_THUNDER_CLIENT int setHDMIResolutionMode(const char* value); +#endif static const char* getHDMIResolutionMode(); public: diff --git a/src/hostif/profiles/STBService/Components_HDMI_Thunder.cpp b/src/hostif/profiles/STBService/Components_HDMI_Thunder.cpp new file mode 100644 index 000000000..263f896ac --- /dev/null +++ b/src/hostif/profiles/STBService/Components_HDMI_Thunder.cpp @@ -0,0 +1,383 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_HDMI_Thunder.cpp + * @brief Thunder-backed implementation of TR069 Components HDMI. + */ + +#include +#include "Components_HDMI.h" + +#define DEV_NAME "HDMI" +#define BASE_NAME "Device.Services.STBService.1.Components.HDMI" +#define UPDATE_FORMAT_STRING "%s.%d.%s" + +#define STATUS_STRING "Status" +#define ENABLE_STRING "Enable" +#define RES_MODE_STRING "ResolutionMode" +#define RES_VAL_STRING "ResolutionValue" +#define NAME_STRING "Name" +#define ENABLED_STRING "Enabled" +#define DISABLED_STRING "Disabled" + +#define THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS "org.rdk.DisplaySettings.getSupportedVideoDisplays" +#define THUNDER_DS_GET_CURRENT_RESOLUTION "org.rdk.DisplaySettings.getCurrentResolution" +#define THUNDER_DS_GET_ENABLE_VIDEO_PORT "org.rdk.DisplaySettings.getEnableVideoPort" +#define THUNDER_DI_FRAMERATE "DisplayInfo.1.framerate" + +char hostIf_STBServiceHDMI::dsHDMIResolutionMode[10] = HDMI_RESOLUTION_MODE_MANUAL; +GHashTable * hostIf_STBServiceHDMI::ifHash = NULL; +GMutex hostIf_STBServiceHDMI::m_mutex; + +void hostIf_STBServiceHDMI::buildPortNameHash() +{ + if (ifHash) closeAllInstances(); + ifHash = g_hash_table_new(NULL, NULL); + + std::string delimitedPorts; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, "[%s] Calling Thunder API: %s\n", __FUNCTION__, THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS); + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, "{}", "supportedVideoDisplays", ",", delimitedPorts)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%d] Failed to get video displays\n", __FUNCTION__, __LINE__); + return; + } + + int devId = 1; + std::istringstream ss(delimitedPorts); + std::string portName; + while (std::getline(ss, portName, ',')) + { + if (!portName.empty()) + { + hostIf_STBServiceHDMI *pInst = new hostIf_STBServiceHDMI(devId, portName); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)devId, pInst); + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s:%d] dev_id=%d portName=%s\n", + __FUNCTION__, __LINE__, devId, portName.c_str()); + devId++; + } + } +} + +hostIf_STBServiceHDMI* hostIf_STBServiceHDMI::getInstance(int dev_id) +{ + if (!ifHash) buildPortNameHash(); + hostIf_STBServiceHDMI *pRet = + (hostIf_STBServiceHDMI*)g_hash_table_lookup(ifHash, (gpointer)(intptr_t)dev_id); + if (!pRet) + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%s:%d]: No instance for dev_id=%d\n", + __FILE__, __FUNCTION__, __LINE__, dev_id); + return pRet; +} + +GList* hostIf_STBServiceHDMI::getAllInstances() +{ + if (ifHash) return g_hash_table_get_keys(ifHash); + return NULL; +} + +void hostIf_STBServiceHDMI::closeInstance(hostIf_STBServiceHDMI *pDev) +{ + if (pDev) { + if (ifHash) g_hash_table_remove(ifHash, (gconstpointer)(intptr_t)pDev->dev_id); + delete pDev; + } +} + +void hostIf_STBServiceHDMI::closeAllInstances() +{ + if (ifHash) { + GList *tmp = g_hash_table_get_values(ifHash), *cur = tmp; + while (cur) { delete (hostIf_STBServiceHDMI*)cur->data; cur = cur->next; } + g_list_free(tmp); + g_hash_table_destroy(ifHash); + ifHash = NULL; + } +} + +void hostIf_STBServiceHDMI::getLock() { g_mutex_init(&m_mutex); g_mutex_lock(&m_mutex); } +void hostIf_STBServiceHDMI::releaseLock() { g_mutex_unlock(&m_mutex); } + +hostIf_STBServiceHDMI::hostIf_STBServiceHDMI(int devid, const std::string& portName) + : dev_id(devid), m_portName(portName) +{ + displayDevice = new hostIf_STBServiceDisplayDevice(devid, portName); + backupEnable = false; + strncpy(backupStatus, " ", sizeof(backupStatus)); + strncpy(backupResolutionValue, " ", sizeof(backupResolutionValue)); + strncpy(backupName, " ", sizeof(backupName)); + bCalledEnable = false; + bCalledStatus = false; + bCalledResolutionValue = false; + bCalledName = false; +} + +hostIf_STBServiceHDMI::~hostIf_STBServiceHDMI() +{ + if (displayDevice) { delete displayDevice; displayDevice = NULL; } +} + +void hostIf_STBServiceHDMI::checkForUpdates(updateCallback) {} + +int hostIf_STBServiceHDMI::handleSetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + // All setters are NOT_HANDLED in Thunder build + (void)paramName; + (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceHDMI::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting HDMI param: %s\n", __FUNCTION__, paramName); + if (strcasecmp(paramName, ENABLE_STRING) == 0) + return getEnable(stMsgData); + if (strcasecmp(paramName, STATUS_STRING) == 0) + return getStatus(stMsgData); + if (strcasecmp(paramName, NAME_STRING) == 0) + return getName(stMsgData); + if (strcasecmp(paramName, RES_MODE_STRING) == 0) { + strncpy(stMsgData->paramValue, getHDMIResolutionMode(), sizeof(stMsgData->paramValue) - 1); + stMsgData->paramValue[sizeof(stMsgData->paramValue) - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; + } + if (strcasecmp(paramName, RES_VAL_STRING) == 0) + return getResolutionValue(stMsgData); + if (strncasecmp(paramName, DISPLAYDEVICE_OBJECT_NAME, strlen(DISPLAYDEVICE_OBJECT_NAME)) == 0) + return displayDevice->handleGetMsg(paramName + strlen(DISPLAYDEVICE_OBJECT_NAME), stMsgData); + return NOT_HANDLED; +} + +void hostIf_STBServiceHDMI::doUpdates(updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; + bool bChanged; + char tmp_buff[PARAM_LEN]; + +#define DO_UPD(fn, param) \ + memset(&msgData,0,sizeof(msgData)); bChanged=false; msgData.instanceNum=dev_id; \ + fn(&msgData,&bChanged); \ + if(bChanged){ snprintf(tmp_buff,PARAM_LEN,UPDATE_FORMAT_STRING,BASE_NAME,dev_id,param); \ + if(mUpdateCallback) mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED,tmp_buff,msgData.paramValue,msgData.paramtype); } + + DO_UPD(getResolutionValue, RES_VAL_STRING) + DO_UPD(getStatus, STATUS_STRING) + DO_UPD(getName, NAME_STRING) +#undef DO_UPD +} + +/* ---- private ---- */ + +int hostIf_STBServiceHDMI::getResolutionValue(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + /* Step 1: get w, h, progressive from getCurrentResolution full response */ + const std::string params = std::string("{\"videoDisplay\":\"") + m_portName + "\"}"; + std::string rawResponse; + + if (!invokeThunderPluginMethod(THUNDER_DS_GET_CURRENT_RESOLUTION, params, rawResponse)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, + "[%s] Thunder %s failed (port=%s)\n", + __FUNCTION__, THUNDER_DS_GET_CURRENT_RESOLUTION, m_portName.c_str()); + return NOK; + } + + int w = 0, h = 0; + bool isProgressive = true; + const char *pos; + + pos = strstr(rawResponse.c_str(), "\"w\":"); + if (pos) w = (int)strtol(pos + 4, NULL, 10); + + pos = strstr(rawResponse.c_str(), "\"h\":"); + if (pos) h = (int)strtol(pos + 4, NULL, 10); + + pos = strstr(rawResponse.c_str(), "\"progressive\":"); + if (pos) + { + pos += strlen("\"progressive\":"); + while (*pos == ' ') pos++; + isProgressive = (strncmp(pos, "true", 4) == 0); + } + + /* Step 2: get frame rate. + * Primary : query DisplayInfo.1.framerate (THUNDER_DI_FRAMERATE). + * Response is {"result":"FramerateXXXX"} where XXXX = framerate * 100. + * e.g. "Framerate5994" -> 59.94 Hz, "Framerate6000" -> 60 Hz. + * Fallback : parse from the "resolution" string in the getCurrentResolution + * response (e.g. "2160p60" -> 60, "1080i50" -> 50). */ + double frameRateD = 0.0; + std::string framerateStr; + if (invokeThunderPluginMethodAndExtractScalarStringResult(THUNDER_DI_FRAMERATE, "{}", framerateStr)) + { + /* Parse "FramerateXXXX" -> XXXX / 100.0 */ + const char *p = framerateStr.c_str(); + while (*p && (*p < '0' || *p > '9')) p++; + if (*p) + frameRateD = strtol(p, NULL, 10) / 100.0; + } + if (frameRateD <= 0.0) + { + const char *resField = strstr(rawResponse.c_str(), "\"resolution\":\""); + if (resField) + { + resField += 14; /* advance past "\"resolution\":\"" */ + while (*resField && *resField != '"') + { + if ((*resField == 'p' || *resField == 'i') && + (*(resField + 1) >= '0') && (*(resField + 1) <= '9')) + { + frameRateD = (double)strtol(resField + 1, NULL, 10); + break; + } + resField++; + } + } + } + + /* Step 3: reconstruct full format string matching original libds format. + * Use decimal notation for non-integer framerates (e.g. 59.94Hz), + * integer for whole-number framerates (e.g. 60Hz). */ + char resStr[PARAM_LEN]; + if (frameRateD > 0.0) + { + long frameRateCentis = (long)(frameRateD * 100.0 + 0.5); + if (frameRateCentis % 100 == 0) + snprintf(resStr, sizeof(resStr), "%dx%d%s/%ldHz", + w, h, isProgressive ? "p" : "i", frameRateCentis / 100); + else + snprintf(resStr, sizeof(resStr), "%dx%d%s/%.2fHz", + w, h, isProgressive ? "p" : "i", frameRateD); + } + else + snprintf(resStr, sizeof(resStr), "%dx%d%s", + w, h, isProgressive ? "p" : "i"); + + strncpy(stMsgData->paramValue, resStr, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + + if (bCalledResolutionValue && pChanged && strcmp(backupResolutionValue, stMsgData->paramValue)) + *pChanged = true; + bCalledResolutionValue = true; + strncpy(backupResolutionValue, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupResolutionValue[_BUF_LEN_16 - 1] = '\0'; + + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, + "[%s] ResolutionValue: %s (port=%s)\n", + __FUNCTION__, stMsgData->paramValue, m_portName.c_str()); + return OK; +} + +/************************************************************ + * Description : Get HDMI port Enable state [MIGRATED to Thunder] + * Thunder API : org.rdk.DisplaySettings.getEnableVideoPort + * Request : { "videoDisplay": "" } + * Response : { "enable": , "success": true } + * Precondition : None + * Input : stMsgData for result return. + pChanged + + * Return : OK -> Success + NOK -> Failure + stMsgData->paramValue -> true : Enabled + -> false : Disabled +************************************************************/ +int hostIf_STBServiceHDMI::getEnable(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool isEnabled = false; + const std::string params = std::string("{\"videoDisplay\":\"") + m_portName + "\"}"; + + if (!invokeThunderPluginMethodAndExtractBoolField( + THUNDER_DS_GET_ENABLE_VIDEO_PORT, params, "enable", isEnabled)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, + "[%s] Thunder %s failed (port=%s)\n", + __FUNCTION__, THUNDER_DS_GET_ENABLE_VIDEO_PORT, m_portName.c_str()); + return NOK; + } + + put_boolean(stMsgData->paramValue, isEnabled); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + + if (bCalledEnable && pChanged && (backupEnable != isEnabled)) + *pChanged = true; + bCalledEnable = true; + backupEnable = isEnabled; + + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, + "[%s] Enable=%d (port=%s)\n", __FUNCTION__, isEnabled, m_portName.c_str()); + return OK; +} + +/************************************************************ + * Description : Get HDMI port status [MIGRATED to Thunder] + * Thunder API : org.rdk.DisplaySettings.getEnableVideoPort + * Request : { "videoDisplay": "" } + * Response : { "enable": , "success": true } + * Precondition : None + * Input : stMsgData for result return. + pChanged + + * Return : OK -> Success + NOK -> Failure + stMsgData->paramValue -> Disabled: HDMI port disabled + -> Enabled: HDMI port enabled + -> Error: Error +************************************************************/ +int hostIf_STBServiceHDMI::getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + int ret = getEnable(stMsgData); + if (ret == OK) + { + const char *statusStr = get_boolean(stMsgData->paramValue) ? ENABLED_STRING : DISABLED_STRING; + strncpy(stMsgData->paramValue, statusStr, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledStatus && pChanged && strcmp(backupStatus, stMsgData->paramValue)) + *pChanged = true; + bCalledStatus = true; + strncpy(backupStatus, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupStatus[_BUF_LEN_16 - 1] = '\0'; + } + return ret; +} + +int hostIf_STBServiceHDMI::getName(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + snprintf(stMsgData->paramValue, PARAM_LEN, "%s", m_portName.c_str()); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledName && pChanged && strcmp(backupName, stMsgData->paramValue)) *pChanged = true; + bCalledName = true; + strncpy(backupName, stMsgData->paramValue, _BUF_LEN_256 - 1); + backupName[_BUF_LEN_256 - 1] = '\0'; + return OK; +} + +const char* hostIf_STBServiceHDMI::getHDMIResolutionMode() +{ + return dsHDMIResolutionMode; +} diff --git a/src/hostif/profiles/STBService/Components_SPDIF.h b/src/hostif/profiles/STBService/Components_SPDIF.h index acd6ec246..375f275c9 100644 --- a/src/hostif/profiles/STBService/Components_SPDIF.h +++ b/src/hostif/profiles/STBService/Components_SPDIF.h @@ -51,6 +51,7 @@ #define DEVSET_COMP_SPDIF_HPP__ #include +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "videoDevice.hpp" #include "videoDFC.hpp" @@ -58,6 +59,7 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include @@ -65,7 +67,9 @@ #include "hostIf_tr69ReqHandler.h" #include "hostIf_updateHandler.h" #include "hostIf_main.h" +#ifndef USE_THUNDER_CLIENT #include "audioOutputPort.hpp" +#endif /* USE_THUNDER_CLIENT */ #ifndef PARAM_LEN #define PARAM_LEN TR69HOSTIFMGR_MAX_PARAM_LEN @@ -79,11 +83,20 @@ class hostIf_STBServiceSPDIF { static GHashTable *ifHash; +#ifdef USE_THUNDER_CLIENT + hostIf_STBServiceSPDIF(int dev_id, const std::string& portName); +#else hostIf_STBServiceSPDIF(int dev_id, device::AudioOutputPort& port); +#endif ~hostIf_STBServiceSPDIF() {}; static GMutex m_mutex; int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; + static void buildPortNameHash(); +#else device::AudioOutputPort& aPort; +#endif bool backupEnable; char backupStatus[_BUF_LEN_16]; @@ -100,17 +113,21 @@ class hostIf_STBServiceSPDIF bool bCalledAudioDelay; private: +#ifndef USE_THUNDER_CLIENT int setEnable(HOSTIF_MsgData_t *stMsgData); int setAlias(HOSTIF_MsgData_t *stMsgData); int setForcePCM(HOSTIF_MsgData_t *stMsgData); int getEnable(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); int getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); +#endif int getAlias(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); +#ifndef USE_THUNDER_CLIENT int getName(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); int getForcePCM(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); int getPassthrough(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); int getAudioDelay(HOSTIF_MsgData_t *stMsgData, bool *pChanged = NULL); +#endif public: static hostIf_STBServiceSPDIF *getInstance(int devid); diff --git a/src/hostif/profiles/STBService/Components_SPDIF_Thunder.cpp b/src/hostif/profiles/STBService/Components_SPDIF_Thunder.cpp new file mode 100644 index 000000000..e0c70b1ee --- /dev/null +++ b/src/hostif/profiles/STBService/Components_SPDIF_Thunder.cpp @@ -0,0 +1,227 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_SPDIF_Thunder.cpp + * @brief Thunder-backed APIs of TR069 Components SPDIF. + */ + +#include +#include "Components_SPDIF.h" + +#define DEV_NAME "SPDIF" +#define BASE_NAME "Device.Services.STBService.1.Components.SPDIF" +#define UPDATE_FORMAT_STRING "%s.%d.%s" + +#define ENABLE_STRING "Enable" +#define STATUS_STRING "Status" +#define ALIAS_STRING "Alias" +#define NAME_STRING "Name" +#define FORCEPCM_STRING "ForcePCM" +#define PASSTHROUGH_STRING "Passthrough" +#define AUDIODELAY_STRING "AudioDelay" + +#define ENABLED_STRING "Enabled" +#define DISABLED_STRING "Disabled" + +#define THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS "org.rdk.DisplaySettings.getSupportedAudioPorts" +#define THUNDER_DS_GET_ENABLE_AUDIO_PORT "org.rdk.DisplaySettings.getEnableAudioPort" +#define THUNDER_DS_GET_AUDIO_ENCODING "org.rdk.DisplaySettings.getAudioEncoding" + +#define THUNDER_DS_GET_AUDIO_DELAY "org.rdk.DisplaySettings.getAudioDelay" + +GHashTable * hostIf_STBServiceSPDIF::ifHash = NULL; +GMutex hostIf_STBServiceSPDIF::m_mutex; + +hostIf_STBServiceSPDIF* hostIf_STBServiceSPDIF::getInstance(int dev_id) +{ + if (!ifHash) + buildPortNameHash(); + + hostIf_STBServiceSPDIF* pRet = + (hostIf_STBServiceSPDIF*)g_hash_table_lookup(ifHash, (gpointer)(intptr_t)dev_id); + + if (!pRet) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, + "[%s:%s:%d]: No instance for dev_id=%d\n", __FILE__, __FUNCTION__, __LINE__, dev_id); + } + return pRet; +} + +void hostIf_STBServiceSPDIF::buildPortNameHash() +{ + if (ifHash) + closeAllInstances(); + + ifHash = g_hash_table_new(NULL, NULL); + + std::string delimitedPorts; + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS, "{}", "supportedAudioPorts", ",", delimitedPorts)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, + "[%s:%d] Failed to get supported audio ports\n", __FUNCTION__, __LINE__); + return; + } + + int devId = 1; + std::istringstream ss(delimitedPorts); + std::string portName; + while (std::getline(ss, portName, ',')) + { + if (!portName.empty() && strcasestr(portName.c_str(), "spdif")) + { + hostIf_STBServiceSPDIF *pInst = new hostIf_STBServiceSPDIF(devId, portName); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)devId, pInst); + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, + "[%s:%d] dev_id=%d portName=%s\n", __FUNCTION__, __LINE__, devId, portName.c_str()); + devId++; + } + } +} + +GList* hostIf_STBServiceSPDIF::getAllInstances() +{ + if(ifHash) + return g_hash_table_get_keys(ifHash); + return NULL; +} + +void hostIf_STBServiceSPDIF::closeInstance(hostIf_STBServiceSPDIF *pDev) +{ + if(pDev) + { + if (ifHash) + g_hash_table_remove(ifHash, (gconstpointer)(intptr_t)pDev->dev_id); + delete pDev; + } +} + +void hostIf_STBServiceSPDIF::closeAllInstances() +{ + if(ifHash) + { + GList* tmp_list = g_hash_table_get_values(ifHash); + GList* current = tmp_list; + + while(current) + { + hostIf_STBServiceSPDIF* pDev = (hostIf_STBServiceSPDIF *)current->data; + current = current->next; + delete pDev; + } + + g_list_free(tmp_list); + g_hash_table_destroy(ifHash); + ifHash = NULL; + } +} + +void hostIf_STBServiceSPDIF::getLock() +{ + g_mutex_init(&hostIf_STBServiceSPDIF::m_mutex); + g_mutex_lock(&hostIf_STBServiceSPDIF::m_mutex); +} + +void hostIf_STBServiceSPDIF::releaseLock() +{ + RDK_LOG(RDK_LOG_INFO,LOG_TR69HOSTIF,"[%s:%d] Unlocking mutex... \n", __FUNCTION__, __LINE__); + g_mutex_unlock(&hostIf_STBServiceSPDIF::m_mutex); +} + +hostIf_STBServiceSPDIF::hostIf_STBServiceSPDIF(int devid, const std::string& portName) + : dev_id(devid), m_portName(portName) +{ + backupEnable = false; + strncpy(backupStatus, " ", sizeof(backupStatus)); + backupForcePCM = false; + backupPassthrough = false; + backupAudioDelay = 0; + + bCalledEnable = false; + bCalledStatus = false; + bCalledAlias = false; + bCalledName = false; + bCalledForcePCM = false; + bCalledPassthrough = false; + bCalledAudioDelay = false; +} + +int hostIf_STBServiceSPDIF::handleSetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + (void)paramName; (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceSPDIF::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + int ret = NOT_HANDLED; + if (strcasecmp(paramName, ALIAS_STRING) == 0) + { + ret = getAlias(stMsgData); + } + return ret; +} + +void hostIf_STBServiceSPDIF::doUpdates(updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; + bool bChanged; + char tmp_buff[PARAM_LEN]; + + memset(&msgData,0,sizeof(msgData)); + memset(tmp_buff,0,PARAM_LEN); + bChanged = false; + msgData.instanceNum=dev_id; + getAlias(&msgData,&bChanged); + if(bChanged) + { + snprintf(tmp_buff, PARAM_LEN, UPDATE_FORMAT_STRING, BASE_NAME, dev_id, ALIAS_STRING); + if(mUpdateCallback) + { + mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED,tmp_buff, msgData.paramValue, msgData.paramtype); + } + } +} + +int hostIf_STBServiceSPDIF::getNumberOfInstances(HOSTIF_MsgData_t *stMsgData) +{ + if (!ifHash) + buildPortNameHash(); + + put_int(stMsgData->paramValue, g_hash_table_size(ifHash)); + stMsgData->paramtype = hostIf_UnsignedIntType; + stMsgData->paramLen = sizeof(unsigned int); + return OK; +} + +int hostIf_STBServiceSPDIF::getAlias(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + (void)pChanged; + strncpy(stMsgData->paramValue, m_portName.c_str(), sizeof(stMsgData->paramValue) - 1); + stMsgData->paramValue[sizeof(stMsgData->paramValue) - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + bCalledAlias = true; + return OK; +} + +/** @} */ +/** @} */ diff --git a/src/hostif/profiles/STBService/Components_VideoDecoder.h b/src/hostif/profiles/STBService/Components_VideoDecoder.h index b2e4ebbc5..dfa6ef10e 100644 --- a/src/hostif/profiles/STBService/Components_VideoDecoder.h +++ b/src/hostif/profiles/STBService/Components_VideoDecoder.h @@ -50,6 +50,7 @@ #ifndef DEVSET_COMP_VIDEODECODER_HPP_ #define DEVSET_COMP_VIDEODECODER_HPP_ +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "videoResolution.hpp" #include "dsTypes.h" @@ -60,6 +61,7 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include "stdlib.h" @@ -78,10 +80,17 @@ class hostIf_STBServiceVideoDecoder { static GHashTable *ifHash; +#ifdef USE_THUNDER_CLIENT + hostIf_STBServiceVideoDecoder(int devid, const std::string& portName = "HDMI0"); +#else hostIf_STBServiceVideoDecoder(int devid); +#endif ~hostIf_STBServiceVideoDecoder() {}; static GMutex m_mutex; int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; +#endif char backupContentAspectRatio[_BUF_LEN_16]; bool backupStandby; @@ -93,11 +102,19 @@ class hostIf_STBServiceVideoDecoder private: int getContentAspectRatio(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#ifdef USE_THUNDER_CLIENT + int getEnable(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); +#endif +#ifndef USE_THUNDER_CLIENT int getX_COMCAST_COM_Standby(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); - int getStatus(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); int setX_COMCAST_COM_Standby(const HOSTIF_MsgData_t *stMsgData); +#endif + int getStatus(HOSTIF_MsgData_t *stMsgData,bool *pChanged = NULL); public: +#ifdef USE_THUNDER_CLIENT + static void buildPortNameHash(); +#endif static hostIf_STBServiceVideoDecoder *getInstance(int dev_id); static void closeInstance(hostIf_STBServiceVideoDecoder *); /** diff --git a/src/hostif/profiles/STBService/Components_VideoDecoder_Thunder.cpp b/src/hostif/profiles/STBService/Components_VideoDecoder_Thunder.cpp new file mode 100644 index 000000000..794f447cd --- /dev/null +++ b/src/hostif/profiles/STBService/Components_VideoDecoder_Thunder.cpp @@ -0,0 +1,241 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_VideoDecoder_Thunder.cpp + * @brief Thunder-backed implementation of TR069 Components VideoDecoder. + */ + +#include +#include "Components_VideoDecoder.h" + +#define DEV_NAME "VideoDecoder" +#define BASE_NAME "Device.Services.STBService.1.Components.VideoDecoder" +#define UPDATE_FORMAT_STRING "%s.%d.%s" + +#define STATUS_STRING "Status" +#define CONTENT_AR_STRING "ContentAspectRatio" +#define COMCAST_STANDBY_STRING "X_COMCAST-COM_Standby" +#define HEVC_STRING "X_RDKCENTRAL-COM_MPEGHPart2" +#define HEVC_PROFILE_PATH ".Capabilities.VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1" +#define NAME_STRING "Name" +#define ENABLE_STRING "Enable" +#define ENABLED_STRING "Enabled" +#define DISABLED_STRING "Disabled" + +#define THUNDER_PM_GET_POWER_STATE "org.rdk.PowerManager.GetPowerState" + +#define THUNDER_DS_GET_DISPLAY_ASPECT_RATIO "org.rdk.DisplaySettings.getDisplayAspectRatio" + +#define THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS "org.rdk.DisplaySettings.getSupportedVideoDisplays" + +GHashTable * hostIf_STBServiceVideoDecoder::ifHash = NULL; +GMutex hostIf_STBServiceVideoDecoder::m_mutex; + +void hostIf_STBServiceVideoDecoder::buildPortNameHash() +{ + if (ifHash) closeAllInstances(); + ifHash = g_hash_table_new(NULL, NULL); + + std::string delimitedPorts; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, "[%s] Calling Thunder API: %s\n", __FUNCTION__, THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS); + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, "{}", "supportedVideoDisplays", ",", delimitedPorts)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%d] Failed to get video displays, using default HDMI0\n", + __FUNCTION__, __LINE__); + hostIf_STBServiceVideoDecoder *pInst = new hostIf_STBServiceVideoDecoder(1, "HDMI0"); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)1, pInst); + return; + } + + int devId = 1; + std::istringstream ss(delimitedPorts); + std::string portName; + while (std::getline(ss, portName, ',')) + { + if (!portName.empty()) + { + hostIf_STBServiceVideoDecoder *pInst = new hostIf_STBServiceVideoDecoder(devId, portName); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)devId, pInst); + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s:%d] dev_id=%d portName=%s\n", + __FUNCTION__, __LINE__, devId, portName.c_str()); + devId++; + } + } +} + +hostIf_STBServiceVideoDecoder* hostIf_STBServiceVideoDecoder::getInstance(int dev_id) +{ + if (!ifHash) buildPortNameHash(); + hostIf_STBServiceVideoDecoder *pRet = + (hostIf_STBServiceVideoDecoder*)g_hash_table_lookup(ifHash, (gpointer)(intptr_t)dev_id); + if (!pRet) + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%s:%d]: No instance for dev_id=%d\n", + __FILE__, __FUNCTION__, __LINE__, dev_id); + return pRet; +} + +GList* hostIf_STBServiceVideoDecoder::getAllInstances() { if(ifHash) return g_hash_table_get_keys(ifHash); return NULL; } + +void hostIf_STBServiceVideoDecoder::closeInstance(hostIf_STBServiceVideoDecoder *pDev) +{ + if (pDev) { + if (ifHash) g_hash_table_remove(ifHash, (gconstpointer)(intptr_t)pDev->dev_id); + delete pDev; + } +} + +void hostIf_STBServiceVideoDecoder::closeAllInstances() +{ + if (ifHash) { + GList *tmp = g_hash_table_get_values(ifHash), *cur = tmp; + while (cur) { delete (hostIf_STBServiceVideoDecoder*)cur->data; cur = cur->next; } + g_list_free(tmp); + g_hash_table_destroy(ifHash); + ifHash = NULL; + } +} + +void hostIf_STBServiceVideoDecoder::getLock() { g_mutex_init(&m_mutex); g_mutex_lock(&m_mutex); } +void hostIf_STBServiceVideoDecoder::releaseLock() { g_mutex_unlock(&m_mutex); } +void hostIf_STBServiceVideoDecoder::checkForUpdates(updateCallback) {} + +hostIf_STBServiceVideoDecoder::hostIf_STBServiceVideoDecoder(int devid, const std::string& portName) + : dev_id(devid), m_portName(portName) +{ + strncpy(backupContentAspectRatio, " ", sizeof(backupContentAspectRatio)); + strncpy(backupVideoDecoderStatus, " ", sizeof(backupVideoDecoderStatus)); + backupStandby = false; + bCalledContentAspectRatio = bCalledStandby = bCalledVideoDecoderStatus = false; +} + +int hostIf_STBServiceVideoDecoder::handleSetMsg(const char *pSetting, HOSTIF_MsgData_t *stMsgData) +{ + (void)pSetting; (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceVideoDecoder::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting VideoDecoder param: %s\n", __FUNCTION__, paramName); + if (strcasecmp(paramName, ENABLE_STRING) == 0) { + put_boolean(stMsgData->paramValue, true); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + return OK; + } else if (strcasecmp(paramName, STATUS_STRING) == 0) { + return getStatus(stMsgData); + } else if (strcasecmp(paramName, NAME_STRING) == 0) { + snprintf(stMsgData->paramValue, PARAM_LEN, "VideoDecoder%s", m_portName.c_str()); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; + } else if (strcasecmp(paramName, CONTENT_AR_STRING) == 0) { + /* ContentAspectRatio: no Thunder equivalent, use DisplaySettings aspect ratio */ + return getContentAspectRatio(stMsgData); + } else if (strcasecmp(paramName, HEVC_STRING) == 0) { + strncpy(stMsgData->paramValue, HEVC_PROFILE_PATH, strlen(HEVC_PROFILE_PATH)+1); + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; + } + return NOT_HANDLED; +} + +void hostIf_STBServiceVideoDecoder::doUpdates(updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; bool bChanged; char tmp_buff[PARAM_LEN]; +#define DO_UPD(fn, param) \ + memset(&msgData,0,sizeof(msgData)); bChanged=false; msgData.instanceNum=dev_id; \ + fn(&msgData,&bChanged); \ + if(bChanged){ snprintf(tmp_buff,PARAM_LEN,UPDATE_FORMAT_STRING,BASE_NAME,dev_id,param); \ + if(mUpdateCallback) mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED,tmp_buff,msgData.paramValue,msgData.paramtype); } + + DO_UPD(getStatus, STATUS_STRING) +#undef DO_UPD +} + +/* ---- private ---- */ + +/************************************************************ + * Description : Get if Video decoder is in Standby or not. + * Precondition : None + * Input : stMsgData for result return. + * pChanged + * + * Return : OK -> Success + * NOK -> Failure + * stMsgData->paramValue -> "Enabled", "Disabled", "Error", "X_COMCAST-COM_Standby" +************************************************************/ +int hostIf_STBServiceVideoDecoder::getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string currentState; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_PM_GET_POWER_STATE, "{}", "currentState", currentState)) + { + strncpy(stMsgData->paramValue, "Error", PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return NOK; + } + + const char *status; + if (currentState == "ON") + status = ENABLED_STRING; + else if (currentState == "STANDBY" || currentState == "LIGHT_SLEEP" || currentState == "DEEP_SLEEP") + status = COMCAST_STANDBY_STRING; + else /* UNKNOWN, OFF */ + status = DISABLED_STRING; + + strncpy(stMsgData->paramValue, status, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(status); + if (bCalledVideoDecoderStatus && pChanged && strcmp(backupVideoDecoderStatus, stMsgData->paramValue)) + *pChanged = true; + bCalledVideoDecoderStatus = true; + strncpy(backupVideoDecoderStatus, stMsgData->paramValue, sizeof(backupVideoDecoderStatus) - 1); + backupVideoDecoderStatus[sizeof(backupVideoDecoderStatus) - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceVideoDecoder::getContentAspectRatio(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string aspectRatio; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, + std::string("{\"videoDisplay\":\"") + m_portName + "\"}", "aspectRatio", aspectRatio)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder %s failed\n", + __FUNCTION__, THUNDER_DS_GET_DISPLAY_ASPECT_RATIO); + aspectRatio = "16:9"; + } + strncpy(stMsgData->paramValue, aspectRatio.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledContentAspectRatio && pChanged && strcmp(backupContentAspectRatio, stMsgData->paramValue)) + *pChanged = true; + bCalledContentAspectRatio = true; + strncpy(backupContentAspectRatio, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupContentAspectRatio[_BUF_LEN_16 - 1] = '\0'; + return OK; +} diff --git a/src/hostif/profiles/STBService/Components_VideoOutput.h b/src/hostif/profiles/STBService/Components_VideoOutput.h index bd65838ad..f6b4a5305 100644 --- a/src/hostif/profiles/STBService/Components_VideoOutput.h +++ b/src/hostif/profiles/STBService/Components_VideoOutput.h @@ -63,6 +63,7 @@ #ifndef DEVSET_COMP_VIDEOOUTPUT_HPP_ #define DEVSET_COMP_VIDEOOUTPUT_HPP_ +#ifndef USE_THUNDER_CLIENT #include "host.hpp" #include "videoResolution.hpp" #include "dsTypes.h" @@ -73,6 +74,7 @@ #include "dsUtl.h" #include "dsError.h" #include "list.hpp" +#endif /* USE_THUNDER_CLIENT */ #include #include #include "stdlib.h" @@ -90,12 +92,22 @@ */ class hostIf_STBServiceVideoOutput { +#ifdef USE_THUNDER_CLIENT + static GHashTable *ifHash; /* dev_id -> hostIf_STBServiceVideoOutput* */ + hostIf_STBServiceVideoOutput(int devid, const std::string& portName); +#else static GHashTable *ifHash; hostIf_STBServiceVideoOutput(int devid, device::VideoOutputPort& port); +#endif ~hostIf_STBServiceVideoOutput() {}; static GMutex m_mutex; int dev_id; +#ifdef USE_THUNDER_CLIENT + std::string m_portName; + static void buildPortNameHash(); +#else device::VideoOutputPort& vPort; +#endif char backupAspectRatioBehaviour[_BUF_LEN_16]; char backupDisplayFormat[_BUF_LEN_16]; diff --git a/src/hostif/profiles/STBService/Components_VideoOutput_Thunder.cpp b/src/hostif/profiles/STBService/Components_VideoOutput_Thunder.cpp new file mode 100644 index 000000000..ceb6feb8a --- /dev/null +++ b/src/hostif/profiles/STBService/Components_VideoOutput_Thunder.cpp @@ -0,0 +1,314 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2026 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 Components_VideoOutput_Thunder.cpp + * @brief Thunder-backed implementation of TR069 Components VideoOutput. + */ + +#include +#include "Components_VideoOutput.h" + +#define DEV_NAME "VideoOutput" +#define BASE_NAME "Device.Services.STBService.1.Components.VideoOutput" +#define UPDATE_FORMAT_STRING "%s.%d.%s" + +#define STATUS_STRING "Status" +#define ENABLE_STRING "Enable" +#define DISPLAY_FORMAT_STRING "DisplayFormat" +#define VIDEO_FORMAT_STRING "VideoFormat" +#define AR_BEHAVIOR_STRING "AspectRatioBehaviour" +#define HDCP_STRING "HDCP" +#define DISPLAY_NAME_STRING "Name" +#define ENABLED_STRING "Enabled" +#define DISABLED_STRING "Disabled" + +#define THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS "org.rdk.DisplaySettings.getSupportedVideoDisplays" +#define THUNDER_DS_GET_CURRENT_RESOLUTION "org.rdk.DisplaySettings.getCurrentResolution" +#define THUNDER_DS_GET_DISPLAY_ASPECT_RATIO "org.rdk.DisplaySettings.getDisplayAspectRatio" +#define THUNDER_DS_GET_ENABLE_VIDEO_PORT "org.rdk.DisplaySettings.getEnableVideoPort" +#define THUNDER_AVO_GET_ZOOM_MODE "org.rdk.AVOutput.getZoomMode" +#define THUNDER_HDCP_GET_STATUS "org.rdk.HdcpProfile.getHDCPStatus" +#define THUNDER_DI_CONNECTED "DisplayInfo.1.connected" + +GHashTable * hostIf_STBServiceVideoOutput::ifHash = NULL; +GMutex hostIf_STBServiceVideoOutput::m_mutex; + +void hostIf_STBServiceVideoOutput::buildPortNameHash() +{ + if (ifHash) closeAllInstances(); + ifHash = g_hash_table_new(NULL, NULL); + + std::string delimitedPorts; + RDK_LOG(RDK_LOG_DEBUG, LOG_TR69HOSTIF, "[%s] Calling Thunder API: %s\n", __FUNCTION__, THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS); + if (!invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, "{}", "supportedVideoDisplays", ",", delimitedPorts)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%d] Failed to get video displays\n", __FUNCTION__, __LINE__); + return; + } + + int devId = 1; + std::istringstream ss(delimitedPorts); + std::string portName; + while (std::getline(ss, portName, ',')) + { + if (!portName.empty()) + { + hostIf_STBServiceVideoOutput *pInst = new hostIf_STBServiceVideoOutput(devId, portName); + g_hash_table_insert(ifHash, (gpointer)(intptr_t)devId, pInst); + devId++; + } + } +} + +hostIf_STBServiceVideoOutput* hostIf_STBServiceVideoOutput::getInstance(int dev_id) +{ + if (!ifHash) buildPortNameHash(); + hostIf_STBServiceVideoOutput *pRet = + (hostIf_STBServiceVideoOutput*)g_hash_table_lookup(ifHash, (gpointer)(intptr_t)dev_id); + if (!pRet) + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s:%s:%d]: No instance for dev_id=%d\n", + __FILE__, __FUNCTION__, __LINE__, dev_id); + return pRet; +} + +GList* hostIf_STBServiceVideoOutput::getAllInstances() { if(ifHash) return g_hash_table_get_keys(ifHash); return NULL; } + +void hostIf_STBServiceVideoOutput::closeInstance(hostIf_STBServiceVideoOutput *pDev) +{ + if (pDev) { + if (ifHash) g_hash_table_remove(ifHash, (gconstpointer)(intptr_t)pDev->dev_id); + delete pDev; + } +} + +void hostIf_STBServiceVideoOutput::closeAllInstances() +{ + if (ifHash) { + GList *tmp = g_hash_table_get_values(ifHash), *cur = tmp; + while (cur) { delete (hostIf_STBServiceVideoOutput*)cur->data; cur = cur->next; } + g_list_free(tmp); + g_hash_table_destroy(ifHash); + ifHash = NULL; + } +} + +void hostIf_STBServiceVideoOutput::getLock() { g_mutex_init(&m_mutex); g_mutex_lock(&m_mutex); } +void hostIf_STBServiceVideoOutput::releaseLock() { g_mutex_unlock(&m_mutex); } +void hostIf_STBServiceVideoOutput::checkForUpdates(updateCallback) {} + +hostIf_STBServiceVideoOutput::hostIf_STBServiceVideoOutput(int devid, const std::string& portName) + : dev_id(devid), m_portName(portName) +{ + strncpy(backupAspectRatioBehaviour, " ", sizeof(backupAspectRatioBehaviour)); + strncpy(backupDisplayFormat, " ", sizeof(backupDisplayFormat)); + strncpy(backupDisplayName, " ", sizeof(backupDisplayName)); + strncpy(backupVideoFormat, " ", sizeof(backupVideoFormat)); + strncpy(backupVideoOutputStatus, " ", sizeof(backupVideoOutputStatus)); + backupHDCP = false; + bCalledAspectRatioBehaviour = bCalledDisplayFormat = bCalledDisplayName = false; + bCalledVideoFormat = bCalledHDCP = bCalledVideoOutputStatus = false; +} + +int hostIf_STBServiceVideoOutput::handleSetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + (void)paramName; (void)stMsgData; + return NOT_HANDLED; +} + +int hostIf_STBServiceVideoOutput::handleGetMsg(const char *paramName, HOSTIF_MsgData_t *stMsgData) +{ + int ret = NOT_HANDLED; + RDK_LOG(RDK_LOG_INFO, LOG_TR69HOSTIF, "[%s] Getting VideoOutput param: %s\n", __FUNCTION__, paramName); + if (strcasecmp(paramName, STATUS_STRING) == 0) + { + ret = getStatus(stMsgData); + } + else if (strcasecmp(paramName, ENABLE_STRING) == 0) + { + put_boolean(stMsgData->paramValue, true); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + ret = OK; + } + else if (strcasecmp(paramName, DISPLAY_FORMAT_STRING) == 0) + { + ret = getDisplayFormat(stMsgData); + } + else if (strcasecmp(paramName, VIDEO_FORMAT_STRING) == 0) + { + ret = getVideoFormat(stMsgData); + } + else if (strcasecmp(paramName, AR_BEHAVIOR_STRING) == 0) + { + ret = getAspectRatioBehaviour(stMsgData); + } + else if (strcasecmp(paramName, HDCP_STRING) == 0) + { + ret = getHDCP(stMsgData); + } + else if (strcasecmp(paramName, DISPLAY_NAME_STRING) == 0) + { + ret = getName(stMsgData); + } + return ret; +} + +void hostIf_STBServiceVideoOutput::doUpdates(updateCallback mUpdateCallback) +{ + HOSTIF_MsgData_t msgData; bool bChanged; char tmp_buff[PARAM_LEN]; +#define DO_UPD(fn, param) \ + memset(&msgData,0,sizeof(msgData)); bChanged=false; msgData.instanceNum=dev_id; \ + fn(&msgData,&bChanged); \ + if(bChanged){ snprintf(tmp_buff,PARAM_LEN,UPDATE_FORMAT_STRING,BASE_NAME,dev_id,param); \ + if(mUpdateCallback) mUpdateCallback(IARM_BUS_TR69HOSTIFMGR_EVENT_VALUECHANGED,tmp_buff,msgData.paramValue,msgData.paramtype); } + + DO_UPD(getStatus, STATUS_STRING) + DO_UPD(getDisplayFormat, DISPLAY_FORMAT_STRING) + DO_UPD(getVideoFormat, VIDEO_FORMAT_STRING) + DO_UPD(getHDCP, HDCP_STRING) +#undef DO_UPD +} + +/* ---- private ---- */ + +int hostIf_STBServiceVideoOutput::getStatus(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool isConnected = false; + + if (!invokeThunderPluginMethodAndExtractBoolField(THUNDER_DI_CONNECTED, "{}", "isconnected", isConnected)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] DisplayInfo.1.connected failed, returning Disabled\n", __FUNCTION__); + // On failure, default to Disabled + isConnected = false; + } + const char *status = isConnected ? ENABLED_STRING : DISABLED_STRING; + strncpy(stMsgData->paramValue, status, PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(status); + if (bCalledVideoOutputStatus && pChanged && strcmp(backupVideoOutputStatus, stMsgData->paramValue)) + *pChanged = true; + bCalledVideoOutputStatus = true; + strncpy(backupVideoOutputStatus, stMsgData->paramValue, sizeof(backupVideoOutputStatus) - 1); + backupVideoOutputStatus[sizeof(backupVideoOutputStatus) - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceVideoOutput::getDisplayFormat(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string res; + const std::string params = std::string("{\"videoDisplay\":\"") + m_portName + "\"}"; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_CURRENT_RESOLUTION, params, "resolution", res)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder %s failed (port=%s)\n", + __FUNCTION__, THUNDER_DS_GET_CURRENT_RESOLUTION, m_portName.c_str()); + return NOK; + } + strncpy(stMsgData->paramValue, res.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledDisplayFormat && pChanged && strcmp(backupDisplayFormat, stMsgData->paramValue)) + *pChanged = true; + bCalledDisplayFormat = true; + strncpy(backupDisplayFormat, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupDisplayFormat[_BUF_LEN_16 - 1] = '\0'; + return OK; +} + + +/************************************************************ + * Description : Get the currently active video output format. + * Precondition : None + * Input : stMsgData for result return. + pChanged + + * Return : OK -> Success + NOK -> Failure + value -> HDMI + DVI. +TODO: Need correct implementation. Here's what TR-135 says: + Comma-separated list of strings. Each entry is a supported display format and + MUST be in the form of “x:y”, such as for example “4:3, 16:9, 14:9". + Need to check with team. +************************************************************/ +int hostIf_STBServiceVideoOutput::getVideoFormat(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string fmt; + const std::string params = std::string("{\"videoDisplay\":\"") + m_portName + "\"}"; + if (!invokeThunderPluginMethodAndExtractStringField( + THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, params, "aspectRatio", fmt)) + fmt = "Unknown"; + strncpy(stMsgData->paramValue, fmt.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledVideoFormat && pChanged && strcmp(backupVideoFormat, stMsgData->paramValue)) + *pChanged = true; + bCalledVideoFormat = true; + strncpy(backupVideoFormat, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupVideoFormat[_BUF_LEN_16 - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceVideoOutput::getAspectRatioBehaviour(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + std::string mode; + if (!invokeThunderPluginMethodAndExtractStringField(THUNDER_AVO_GET_ZOOM_MODE, "{}", "zoomSetting", mode)) + mode = "None"; + strncpy(stMsgData->paramValue, mode.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + if (bCalledAspectRatioBehaviour && pChanged && strcmp(backupAspectRatioBehaviour, stMsgData->paramValue)) + *pChanged = true; + bCalledAspectRatioBehaviour = true; + strncpy(backupAspectRatioBehaviour, stMsgData->paramValue, _BUF_LEN_16 - 1); + backupAspectRatioBehaviour[_BUF_LEN_16 - 1] = '\0'; + return OK; +} + +int hostIf_STBServiceVideoOutput::getHDCP(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + bool hdcpEnabled = false; + if (!invokeThunderPluginMethodAndExtractBoolField(THUNDER_HDCP_GET_STATUS, "{}", "isHDCPCompliant", hdcpEnabled)) + { + RDK_LOG(RDK_LOG_WARN, LOG_TR69HOSTIF, "[%s] Thunder getHDCPStatus failed, assuming not compliant\n", __FUNCTION__); + } + put_boolean(stMsgData->paramValue, hdcpEnabled); + stMsgData->paramtype = hostIf_BooleanType; + stMsgData->paramLen = sizeof(bool); + if (bCalledHDCP && pChanged && (backupHDCP != hdcpEnabled)) *pChanged = true; + bCalledHDCP = true; + backupHDCP = hdcpEnabled; + return OK; +} + +int hostIf_STBServiceVideoOutput::getName(HOSTIF_MsgData_t *stMsgData, bool *pChanged) +{ + (void)pChanged; + strncpy(stMsgData->paramValue, m_portName.c_str(), PARAM_LEN); + stMsgData->paramValue[PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = strlen(stMsgData->paramValue); + return OK; +} diff --git a/src/hostif/profiles/STBService/Makefile.am b/src/hostif/profiles/STBService/Makefile.am index 33909413e..d816bff8c 100644 --- a/src/hostif/profiles/STBService/Makefile.am +++ b/src/hostif/profiles/STBService/Makefile.am @@ -21,9 +21,13 @@ SUBDIRS = DIST_SUBDIRS = -AM_CXXFLAGS = -I$(top_srcdir)/src/hostif/include -I$(top_srcdir)/src/hostif/handlers/include -I./include $(GLIB_CFLAGS) $(G_THREAD_CFLAGS) \ --I=/usr/include/rdk/ds-hal/ \ --I=/usr/include/rdk/ds/ +AM_CXXFLAGS = -I$(top_srcdir)/src/hostif/include -I$(top_srcdir)/src/hostif/handlers/include -I./include $(GLIB_CFLAGS) $(G_THREAD_CFLAGS) + +if WITH_THUNDER_CLIENT +else +AM_CXXFLAGS += -I=/usr/include/rdk/ds-hal/ \ + -I=/usr/include/rdk/ds/ +endif if WITH_XRDK_SDCARD_PROFILE AM_CXXFLAGS += $(XRDK_SDCARD_PROFILE_FLAG) @@ -34,6 +38,17 @@ AM_CXXFLAGS += $(XRDK_EMMC_PROFILE_FLAG) endif noinst_LTLIBRARIES = libstbservice.la + +if WITH_THUNDER_CLIENT +AM_CXXFLAGS += -DUSE_THUNDER_CLIENT +libstbservice_la_SOURCES = Components_AudioOutput_Thunder.cpp \ + Components_SPDIF_Thunder.cpp \ + Components_HDMI_Thunder.cpp \ + Components_DisplayDevice_Thunder.cpp \ + Components_VideoOutput_Thunder.cpp \ + Components_VideoDecoder_Thunder.cpp \ + Capabilities_Thunder.cpp +else libstbservice_la_SOURCES = Components_AudioOutput.cpp \ Components_SPDIF.cpp \ Components_HDMI.cpp \ @@ -41,6 +56,7 @@ libstbservice_la_SOURCES = Components_AudioOutput.cpp \ Components_VideoOutput.cpp \ Components_VideoDecoder.cpp \ Capabilities.cpp +endif if WITH_XRDK_SDCARD_PROFILE libstbservice_la_SOURCES += Components_XrdkSDCard.cpp endif diff --git a/src/hostif/profiles/STBService/docs/README.md b/src/hostif/profiles/STBService/docs/README.md index 07459a224..fdf024cb4 100644 --- a/src/hostif/profiles/STBService/docs/README.md +++ b/src/hostif/profiles/STBService/docs/README.md @@ -2,7 +2,14 @@ ## Overview -The STBService profile implements the TR-135 (Set-top Box Service) object tree `Device.Services.STBService.1.*`. It exposes the AV capabilities, output port state, and hardware health metrics of an RDK set-top box to TR-069 ACS and WebPA. All hardware access goes through the RDK Device Settings (DS) HAL layer (`libdshal`) using C++ wrapper objects from `device::Host`, `device::VideoOutputPort`, `device::AudioOutputPort`, and related classes. SD card and eMMC health data additionally use the `rdkStorageMgr` HAL. +The STBService profile implements the TR-135 (Set-top Box Service) object tree `Device.Services.STBService.1.*`. It exposes AV capabilities, output port state, and hardware health metrics of an RDK set-top box to TR-069 ACS and WebPA. + +Current state is mixed: +- Most component handlers still access hardware through the RDK Device Settings (DS) HAL (`libdshal`) using `device::Host`, `device::VideoOutputPort`, `device::AudioOutputPort`, and related classes. +- Migration target is Thunder JSON-RPC plugin integration for STBService component reads and writes while preserving TR-69 request/response semantics. +- SD card and eMMC health data continue to use `rdkStorageMgr` HAL and are out of scope for Thunder migration in this contract change. + +See `thunder-migration-mapping.md` for component-to-plugin mapping, method candidates, and known no-equivalent gaps. --- @@ -64,6 +71,25 @@ graph TB --- +## Thunder Migration Contract Notes + +The STBService contract migration aligns component domains to Thunder plugin ownership: + +- `org.rdk.DisplaySettings`: AudioOutput, SPDIF, HDMI, and port-oriented VideoOutput operations +- `org.rdk.AVOutput`: TV-wide picture/display mode operations where parameters are not port-scoped +- `org.rdk.DisplayInfo`: Display connection and resolution state (`connected`, `width`, `height`, HDR/HDCP-related display info) +- `org.rdk.HdcpProfile`: HDCP status and version/compliance state +- `org.rdk.PowerManager`: Power state controls and status for decoder/power-related behavior + +Instance lifecycle expectations: + +- Port-based components (for example AudioOutput, SPDIF, HDMI/VideoOutput) should enumerate ports from Thunder and create one instance per discovered port. +- Non-port domains should expose a single logical instance. + +For unresolved parameter mappings, handlers must return explicit fault outcomes rather than silently falling back to stale values. + +--- + ## TR-181/TR-135 Parameter Coverage ### `STBService.1.Capabilities` diff --git a/src/hostif/profiles/STBService/docs/thunder-migration-mapping.md b/src/hostif/profiles/STBService/docs/thunder-migration-mapping.md new file mode 100644 index 000000000..285de8b40 --- /dev/null +++ b/src/hostif/profiles/STBService/docs/thunder-migration-mapping.md @@ -0,0 +1,45 @@ +# STBService Thunder Migration Mapping + +## Scope + +This document inventories STBService components that currently use libds (`device::` HAL) and maps each component to the target Thunder plugin domain. + +Source review performed against: +- `src/hostif/profiles/STBService/Components_*.cpp` +- `src/hostif/profiles/STBService/Components_*.h` +- `openspec/specs/profile-stbservice-contract/tr69hostif-thunder-migration.prompt.md` + +## Component-to-Plugin Mapping + +| Component | Current backend pattern | Target Thunder plugin | Locked methods / response fields | +|---|---|---|---| +| `Components_AudioOutput` | `device::AudioOutputPort`, `device::Host::getAudioOutputPorts()` | `org.rdk.DisplaySettings` | `getSupportedAudioPorts`→`supportedAudioPorts`; `getEnableAudioPort`→`enable` / `setEnableAudioPort`; `getMuted`→`muted` / `setMuted`; `getVolumeLevel`→`volumeLevel` / `setVolumeLevel`; `getAudioEncoding`→`audioEncoding` / `setAudioEncoding`; `getAudioFormat`→`audioFormat`; `getSoundMode`→`soundMode` / `setSoundMode`; `getAudioCompression`→`compressionLevel` / `setAudioCompression`; `getDialogEnhancement`→`dialogEnhancementlevel` / `setDialogEnhancement` | +| `Components_SPDIF` | `device::AudioOutputPort`, SPDIF-specific port selection | `org.rdk.DisplaySettings` | `getSupportedAudioPorts`→`supportedAudioPorts` (SPDIF filter); `getEnableAudioPort`→`enable` / `setEnableAudioPort`; `getAudioEncoding`→`audioEncoding` / `setAudioEncoding` (PCM=ForcePCM, non-PCM=passthrough); `getAudioDelay`→`audioDelay` | +| `Components_HDMI` | `device::VideoOutputPort`, resolution control via DS HAL | `org.rdk.DisplaySettings` | `getSupportedVideoDisplays`→`supportedVideoDisplays` (port discovery); `getCurrentResolution`→`resolution` / `setCurrentResolution`; enable/disable: `getEnableAudioPort`-style on video port using `getVideoPortStatusInfo`→`isConnected`; `getDefaultResolution`→`defaultResolution` | +| `Components_DisplayDevice` | `device::VideoOutputPort` display capabilities | `org.rdk.DisplayInfo` | `DisplayInfo.1.displayinfo`→`connected`, `width`, `height`, `hdrtype`, `hdcpprotection`; `DisplayInfo.1.connected`→bool; `DisplayInfo.1.width` / `DisplayInfo.1.height` (pixel resolution for SupportedResolutions/PreferredResolution strings). EDID fields (`EEDID`, `X_COMCAST-COM_EDID`, `EDID_BYTES`) have no Thunder equivalent — guard with `#if 0` | +| `Components_VideoOutput` | `device::VideoOutputPort`, HDCP status | `org.rdk.DisplaySettings` + `org.rdk.HdcpProfile` | DisplaySettings: `getSupportedVideoDisplays`→port discovery; `getCurrentResolution`→`resolution` (DisplayFormat); `getVideoFormat`→`videoFormat`. HdcpProfile: `getHDCPStatus`→`isHDCPEnabled`, `currentHDCPVersion`, `isHDCPCompliant`, `isConnected`. AspectRatioBehaviour: `org.rdk.AVOutput.getZoomMode`→`zoomMode` | +| `Components_VideoDecoder` | `device::Host::getPowerMode`/`setPowerMode`, `device::VideoOutputPort::Display::getAspectRatio` | `org.rdk.PowerManager` + `org.rdk.DisplayInfo` | PowerManager: `GetPowerState`→`currentState` (`POWER_STATE_STANDBY`=standby on/off) / `SetPowerState`; DisplayInfo: `DisplayInfo.1.displayinfo`→`width`/`height` for aspect ratio approximation. ContentAspectRatio and HEVC flag: no direct Thunder equivalent — return `NOT_HANDLED` | +| `Components_XrdkEMMC` | `rdkStorageMgr` (not libds) | No Thunder migration in this change | Continue using storage HAL-backed path | +| `Components_XrdkSDCard` | `rdkStorageMgr` (not libds) | No Thunder migration in this change | Continue using storage HAL-backed path | + +## libds Usage Inventory Notes + +- Port-reference fields in headers have been replaced with Thunder-compatible instance state (`m_portName`) for migrated components. +- Runtime instance creation for migrated components enumerates ports via Thunder (for example `getSupportedAudioPorts` / `getSupportedVideoDisplays`). +- Thunder-backed implementations now live in `Components_*_Thunder.cpp` under `src/hostif/profiles/STBService`. + +## No Direct Thunder Equivalent (Current Gaps) + +The following parameter areas have no confirmed one-to-one Thunder API in the migration guide and require explicit fallback/fault behavior during implementation: + +- DisplayDevice EDID-oriented fields: + - `EEDID`, `X_COMCAST-COM_EDID`, `EDID_BYTES` +- DisplayDevice sink capability fields: + - `CECSupport`, `AutoLipSyncSupport`, `HDMI3DPresent`, `VideoLatency` +- VideoDecoder codec capability-style fields where source is DS capability introspection rather than a direct Thunder property. + +## Recommended Fault Handling for Unmapped Fields + +- Return explicit non-writable or invalid-parameter style faults for unsupported SET requests. +- Return explicit backend failure faults for unmapped/unsupported GET requests rather than stale synthetic defaults. +- Keep parameter presence stable in the TR-69 tree; only backend behavior and fault semantics change. \ No newline at end of file diff --git a/src/hostif/profiles/STBService/gtest/Makefile.am b/src/hostif/profiles/STBService/gtest/Makefile.am new file mode 100644 index 000000000..9efab6eb2 --- /dev/null +++ b/src/hostif/profiles/STBService/gtest/Makefile.am @@ -0,0 +1,68 @@ +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2024 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. + +AUTOMAKE_OPTIONS = subdir-objects + +bin_PROGRAMS = stbservice_thunder_gtest + +# -DUSE_THUNDER_CLIENT : compile the Thunder-backed implementations +# -DUNIT_TEST : suppress any production-only #ifdefs +# -DGTEST_ENABLE : enable gtest-specific code paths +COMMON_CPPFLAGS = \ + -std=c++11 \ + -DGTEST_ENABLE \ + -DUNIT_TEST \ + -DUSE_THUNDER_CLIENT \ + -I/usr/include \ + -I/usr/include/cjson \ + -I/usr/include/glib-2.0 \ + -I/usr/lib/x86_64-linux-gnu/glib-2.0/include \ + -I$(TOP_DIR)/src/hostif/include \ + -I$(TOP_DIR)/src/hostif/profiles/STBService \ + -I$(TOP_DIR)/src/hostif/handlers/include \ + -I$(TOP_DIR)/src/unittest/stubs \ + -I/usr/local/include + +COMMON_LDADD = \ + -lgtest \ + -lgtest_main \ + -lgmock \ + -lgmock_main \ + -lgcov \ + $(GLIB_LIBS) \ + -lcjson + +COMMON_CXXFLAGS = \ + -frtti \ + $(GLIB_CFLAGS) \ + -fprofile-arcs \ + -ftest-coverage + +stbservice_thunder_gtest_SOURCES = \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_AudioOutput_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_SPDIF_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_DisplayDevice_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_HDMI_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoDecoder_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Components_VideoOutput_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/Capabilities_Thunder.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/gtest/thunder_plugin_stub.cpp \ + $(TOP_DIR)/src/hostif/profiles/STBService/gtest/gtest_stbservice_thunder.cpp + +stbservice_thunder_gtest_CPPFLAGS = $(COMMON_CPPFLAGS) +stbservice_thunder_gtest_LDADD = $(COMMON_LDADD) +stbservice_thunder_gtest_CXXFLAGS = $(COMMON_CXXFLAGS) diff --git a/src/hostif/profiles/STBService/gtest/gtest_stbservice_thunder.cpp b/src/hostif/profiles/STBService/gtest/gtest_stbservice_thunder.cpp new file mode 100644 index 000000000..33d88e6e2 --- /dev/null +++ b/src/hostif/profiles/STBService/gtest/gtest_stbservice_thunder.cpp @@ -0,0 +1,1700 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 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 gtest_stbservice_thunder.cpp + * + * L1 unit tests for the Thunder-backed STBService components: + * - hostIf_STBServiceAudioInterface (Components_AudioOutput_Thunder.cpp) + * - hostIf_STBServiceSPDIF (Components_SPDIF_Thunder.cpp) + * - hostIf_STBServiceDisplayDevice (Components_DisplayDevice_Thunder.cpp) + * - hostIf_STBServiceVideoDecoder (Components_VideoDecoder_Thunder.cpp) + * + * Thunder calls are intercepted by ThunderStub (thunder_plugin_stub.cpp). + * Each test fixture sets up canned responses before calling the SUT via the + * public handleGetMsg / handleSetMsg API. + */ + +#include +#include +#include +#include + +#include "hostIf_tr69ReqHandler.h" +#include "hostIf_main.h" +#include "Components_AudioOutput.h" +#include "Components_SPDIF.h" +#include "Components_DisplayDevice.h" +#include "Components_VideoDecoder.h" +#include "Components_VideoOutput.h" +#include "Components_HDMI.h" +#include "Capabilities.h" + +/* ThunderStub API (defined in thunder_plugin_stub.cpp) */ +namespace ThunderStub { + void setBool(const std::string& method, bool success, bool value); + void setString(const std::string& method, bool success, const std::string& value); + void setInt(const std::string& method, bool success, int value); + void setRaw(const std::string& method, bool success, const std::string& response); + void clear(); +} + +/* Thunder method name constants mirrored here to avoid including internal .cpp headers */ +#define THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS "org.rdk.DisplaySettings.getSupportedAudioPorts" +#define THUNDER_DS_GET_ENABLE_AUDIO_PORT "org.rdk.DisplaySettings.getEnableAudioPort" +#define THUNDER_DS_GET_MUTED "org.rdk.DisplaySettings.getMuted" +#define THUNDER_DS_GET_VOLUME_LEVEL "org.rdk.DisplaySettings.getVolumeLevel" +#define THUNDER_DS_GET_AUDIO_ENCODING "org.rdk.DisplaySettings.getAudioEncoding" +#define THUNDER_DS_GET_AUDIO_FORMAT "org.rdk.DisplaySettings.getAudioFormat" +#define THUNDER_DS_GET_SOUND_MODE "org.rdk.DisplaySettings.getSoundMode" +#define THUNDER_DS_GET_MS12_AUDIO_COMPRESSION "org.rdk.DisplaySettings.getMS12AudioCompression" + +#define THUNDER_DI_CONNECTED "DisplayInfo.1.connected" +#define THUNDER_DS_GET_SUPPORTED_RESOLUTIONS "org.rdk.DisplaySettings.getSupportedResolutions" +#define THUNDER_DS_GET_DEFAULT_RESOLUTION "org.rdk.DisplaySettings.getDefaultResolution" +#define THUNDER_DS_READ_EDID "org.rdk.DisplaySettings.readEDID" + +#define THUNDER_PM_GET_POWER_STATE "org.rdk.PowerManager.GetPowerState" +#define THUNDER_DS_GET_DISPLAY_ASPECT_RATIO "org.rdk.DisplaySettings.getDisplayAspectRatio" +#define THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS "org.rdk.DisplaySettings.getSupportedVideoDisplays" + +#define THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS "org.rdk.DisplaySettings.getSupportedVideoCodingFormats" +#define THUNDER_DS_GET_VIDEO_CODEC_INFO "org.rdk.DisplaySettings.getVideoCodecInfo" +#define THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS "org.rdk.DisplaySettings.getSupportedSettopResolutions" + +#define THUNDER_DS_GET_CURRENT_RESOLUTION "org.rdk.DisplaySettings.getCurrentResolution" +#define THUNDER_DS_GET_ENABLE_VIDEO_PORT "org.rdk.DisplaySettings.getEnableVideoPort" +#define THUNDER_AVO_GET_ZOOM_MODE "org.rdk.AVOutput.getZoomMode" +#define THUNDER_HDCP_GET_STATUS "org.rdk.HdcpProfile.getHDCPStatus" +#define THUNDER_DI_FRAMERATE "DisplayInfo.1.framerate" + +/* Helpers */ +static HOSTIF_MsgData_t makeMsg() +{ + HOSTIF_MsgData_t m; + memset(&m, 0, sizeof(m)); + return m; +} + +/* ==================================================================== + * AudioOutput Tests + * ==================================================================== */ + +class AudioOutputThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceAudioInterface *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + hostIf_STBServiceAudioInterface::closeAllInstances(); + + /* buildPortNameHash() will be called from getInstance() and will + * query getSupportedAudioPorts. Return "HDMI0" so that one + * instance (dev_id=1, portName="HDMI0") is created. */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS, + true, "HDMI0"); + + m_iface = hostIf_STBServiceAudioInterface::getInstance(1); + ASSERT_NE(m_iface, nullptr) << "getInstance returned nullptr"; + } + + void TearDown() override + { + hostIf_STBServiceAudioInterface::closeAllInstances(); + ThunderStub::clear(); + } +}; + +/* getStatus: port enabled and un-muted → "Enabled" */ +TEST_F(AudioOutputThunderTest, GetStatus_Enabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, true, true); + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Enabled"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getStatus: port enabled but muted → "Muted" */ +TEST_F(AudioOutputThunderTest, GetStatus_Muted) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, true, true); + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Muted"); +} + +/* getStatus: port disabled → "Disabled" */ +TEST_F(AudioOutputThunderTest, GetStatus_Disabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Disabled"); +} + +/* getStatus: Thunder call fails → NOK */ +TEST_F(AudioOutputThunderTest, GetStatus_ThunderFailure) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, NOK); +} + +/* getEnable: always returns OK with boolean true (port is present in the list) */ +TEST_F(AudioOutputThunderTest, GetEnable_AlwaysTrue) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + /* put_boolean stores '1' for true */ + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* getName: returns "AudioOutputPort" */ +TEST_F(AudioOutputThunderTest, GetName_Format) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* portName="HDMI0", dev_id=1 → "AudioOutputPortHDMI01" */ + EXPECT_STREQ(msg.paramValue, "AudioOutputPortHDMI01"); +} + +/* getCancelMute: muted=true → paramValue '1' (boolean true) */ +TEST_F(AudioOutputThunderTest, GetCancelMute_Muted) +{ + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("CancelMute", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* getCancelMute: muted=false → paramValue '0' (boolean false) */ +TEST_F(AudioOutputThunderTest, GetCancelMute_NotMuted) +{ + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("CancelMute", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramValue[0], '0'); +} + +/* getCancelMute: Thunder failure → NOK */ +TEST_F(AudioOutputThunderTest, GetCancelMute_ThunderFailure) +{ + ThunderStub::setBool(THUNDER_DS_GET_MUTED, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("CancelMute", &msg); + + EXPECT_EQ(rc, NOK); +} + +/* getX_COMCAST_COM_AudioStereoMode: maps soundMode string through */ +TEST_F(AudioOutputThunderTest, GetAudioStereoMode_Stereo) +{ + ThunderStub::setString(THUNDER_DS_GET_SOUND_MODE, true, "STEREO"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioStereoMode", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "STEREO"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getX_COMCAST_COM_AudioStereoMode: Thunder failure → NOK */ +TEST_F(AudioOutputThunderTest, GetAudioStereoMode_ThunderFailure) +{ + ThunderStub::setString(THUNDER_DS_GET_SOUND_MODE, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioStereoMode", &msg); + + EXPECT_EQ(rc, NOK); +} + +/* getX_COMCAST_COM_AudioCompression: maps compressionlevel int */ +TEST_F(AudioOutputThunderTest, GetAudioCompression_Value) +{ + ThunderStub::setInt(THUNDER_DS_GET_MS12_AUDIO_COMPRESSION, true, 3); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioCompression", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_UnsignedIntType); + EXPECT_EQ(*reinterpret_cast(msg.paramValue), 3u); +} + +/* getX_COMCAST_COM_AudioEncoding: maps encoding string through */ +TEST_F(AudioOutputThunderTest, GetAudioEncoding_AC3) +{ + ThunderStub::setString(THUNDER_DS_GET_AUDIO_ENCODING, true, "AC3"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioEncoding", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "AC3"); +} + +/* getX_COMCAST_COM_AudioFormat: maps audioFormat string through */ +TEST_F(AudioOutputThunderTest, GetAudioFormat) +{ + /* getX_COMCAST_COM_AudioFormat calls THUNDER_DS_GET_AUDIO_ENCODING internally */ + ThunderStub::setString(THUNDER_DS_GET_AUDIO_ENCODING, true, "PCM"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("AudioFormat", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "PCM"); +} + +/* handleSetMsg: all setters are NOT_HANDLED in Thunder build */ +TEST_F(AudioOutputThunderTest, HandleSetMsg_AllReturnsNotHandled) +{ + const char *setParams[] = { + "CancelMute", "AudioLevel", "X_COMCAST-COM_AudioEncoding", + "X_COMCAST-COM_AudioStereoMode", "X_COMCAST-COM_AudioCompression", + "X_COMCAST-COM_DialogEnhancement", "X_COMCAST-COM_AudioDB", + "X_COMCAST-COM_AudioLoopThru" + }; + HOSTIF_MsgData_t msg = makeMsg(); + for (const char *p : setParams) + { + int rc = m_iface->handleSetMsg(p, &msg); + EXPECT_EQ(rc, NOT_HANDLED) << "Expected NOT_HANDLED for setter: " << p; + } +} + +/* handleGetMsg: unknown parameter name → NOT_HANDLED */ +TEST_F(AudioOutputThunderTest, HandleGetMsg_UnknownParam_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioDB", &msg); + EXPECT_EQ(rc, NOT_HANDLED); +} + +/* getStatus with pChanged: detects change */ +TEST_F(AudioOutputThunderTest, GetStatus_WithChangeDetection) +{ + /* First call: Enabled */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, true, true); + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, false); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Status", &msg1); + + /* Second call: Muted (changed) */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_AUDIO_PORT, true, true); + ThunderStub::setBool(THUNDER_DS_GET_MUTED, true, true); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "Muted"); +} + +/* getName with pChanged: no change */ +TEST_F(AudioOutputThunderTest, GetName_NoChange) +{ + /* First call */ + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Name", &msg1); + + /* Second call: name doesn't change */ + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "AudioOutputPortHDMI01"); +} + +/* getAudioLevel with pChanged: detects change */ +TEST_F(AudioOutputThunderTest, GetAudioLevel_WithChangeDetection) +{ + /* First call: level 75 */ + ThunderStub::setInt(THUNDER_DS_GET_VOLUME_LEVEL, true, 75); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("AudioLevel", &msg1); + + /* Second call: level 50 (changed) */ + ThunderStub::setInt(THUNDER_DS_GET_VOLUME_LEVEL, true, 50); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("AudioLevel", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(get_uint(msg2.paramValue), 50u); +} + +/* getX_COMCAST_COM_AudioEncoding with pChanged: detects change */ +TEST_F(AudioOutputThunderTest, GetAudioEncoding_WithChangeDetection) +{ + /* First call: AC3 */ + ThunderStub::setString(THUNDER_DS_GET_AUDIO_ENCODING, true, "AC3"); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("X_COMCAST-COM_AudioEncoding", &msg1); + + /* Second call: PCM (changed) */ + ThunderStub::setString(THUNDER_DS_GET_AUDIO_ENCODING, true, "PCM"); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioEncoding", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "PCM"); +} + +/* ==================================================================== + * SPDIF Tests + * ==================================================================== */ + +class SPDIFThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceSPDIF *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + hostIf_STBServiceSPDIF::closeAllInstances(); + + /* buildPortNameHash filters for names containing "spdif" (case-insensitive). + * Return "SPDIF0" so one instance (dev_id=1, portName="SPDIF0") is created. */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS, + true, "SPDIF0"); + + m_iface = hostIf_STBServiceSPDIF::getInstance(1); + ASSERT_NE(m_iface, nullptr) << "getInstance returned nullptr"; + } + + void TearDown() override + { + hostIf_STBServiceSPDIF::closeAllInstances(); + ThunderStub::clear(); + } +}; + +/* getAlias: returns the port name stored at construction */ +TEST_F(SPDIFThunderTest, GetAlias_ReturnsPortName) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Alias", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "SPDIF0"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getEnable / getStatus: NOT_HANDLED in Thunder build */ +TEST_F(SPDIFThunderTest, HandleGetMsg_Enable_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("Enable", &msg), NOT_HANDLED); +} + +TEST_F(SPDIFThunderTest, HandleGetMsg_Status_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("Status", &msg), NOT_HANDLED); +} + +TEST_F(SPDIFThunderTest, HandleGetMsg_ForcePCM_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("ForcePCM", &msg), NOT_HANDLED); +} + +/* handleSetMsg: all setters are NOT_HANDLED */ +TEST_F(SPDIFThunderTest, HandleSetMsg_AllReturnsNotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleSetMsg("Enable", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("Alias", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("ForcePCM",&msg), NOT_HANDLED); +} + +/* handleGetMsg: unknown parameter → NOT_HANDLED */ +TEST_F(SPDIFThunderTest, HandleGetMsg_UnknownParam_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("X_UnknownParam", &msg), NOT_HANDLED); +} + +/* getAlias with pChanged: detects no change */ +TEST_F(SPDIFThunderTest, GetAlias_NoChange) +{ + /* First call */ + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Alias", &msg1); + + /* Second call: alias doesn't change */ + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Alias", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "SPDIF0"); +} + +/* Test getInstance with multiple ports */ +TEST_F(SPDIFThunderTest, GetInstance_MultipleDevices) +{ + /* Close existing instances first */ + hostIf_STBServiceSPDIF::closeAllInstances(); + + /* Return two SPDIF ports */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_AUDIO_PORTS, true, "SPDIF0,SPDIF1"); + + /* Get first instance */ + hostIf_STBServiceSPDIF *inst1 = hostIf_STBServiceSPDIF::getInstance(1); + ASSERT_NE(inst1, nullptr); + + /* Get second instance */ + hostIf_STBServiceSPDIF *inst2 = hostIf_STBServiceSPDIF::getInstance(2); + ASSERT_NE(inst2, nullptr); + + /* Verify they're different instances */ + EXPECT_NE(inst1, inst2); + + hostIf_STBServiceSPDIF::closeAllInstances(); +} + +/* Test getInstance with invalid dev_id */ +TEST_F(SPDIFThunderTest, GetInstance_InvalidDevId_ReturnsNull) +{ + hostIf_STBServiceSPDIF *inst = hostIf_STBServiceSPDIF::getInstance(999); + EXPECT_EQ(inst, nullptr); +} + +/* Test getAllInstances */ +TEST_F(SPDIFThunderTest, GetAllInstances_ReturnsKeys) +{ + GList *list = hostIf_STBServiceSPDIF::getAllInstances(); + ASSERT_NE(list, nullptr); + + /* Should have at least one entry (dev_id=1 from setup) */ + EXPECT_GE(g_list_length(list), 1u); + + g_list_free(list); +} + +/* ==================================================================== + * DisplayDevice Tests + * ==================================================================== */ + +class DisplayDeviceThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceDisplayDevice *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + /* DisplayDevice creates its instance directly (not via port list). + * Instantiate with dev_id=1, portName="HDMI0". */ + m_iface = new hostIf_STBServiceDisplayDevice(1, "HDMI0"); + ASSERT_NE(m_iface, nullptr); + } + + void TearDown() override + { + delete m_iface; + m_iface = nullptr; + ThunderStub::clear(); + } +}; + +/* getStatus: display connected → "Present" */ +TEST_F(DisplayDeviceThunderTest, GetStatus_Present) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Present"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getStatus: display not connected → "Absent" */ +TEST_F(DisplayDeviceThunderTest, GetStatus_Absent) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Absent"); +} + +/* getStatus: Thunder call fails → still OK but returns "Absent" (connected=false) */ +TEST_F(DisplayDeviceThunderTest, GetStatus_ThunderFailure_ReturnsAbsent) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Absent"); +} + +/* handleSetMsg: always NOT_HANDLED */ +TEST_F(DisplayDeviceThunderTest, HandleSetMsg_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleSetMsg("Status", &msg), NOT_HANDLED); +} + +/* ==================================================================== + * VideoDecoder Tests + * ==================================================================== */ + +class VideoDecoderThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceVideoDecoder *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + hostIf_STBServiceVideoDecoder::closeAllInstances(); + + /* buildPortNameHash queries getSupportedVideoDisplays then creates + * instance for each display. Return "HDMI0" → dev_id=2 (first + * entry reserved for "VideoDecoderHDMI0" at dev_id=1 in the impl). + * Use getInstance(1) which triggers buildPortNameHash. */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, true, "HDMI0"); + + m_iface = hostIf_STBServiceVideoDecoder::getInstance(1); + ASSERT_NE(m_iface, nullptr) << "getInstance returned nullptr"; + } + + void TearDown() override + { + hostIf_STBServiceVideoDecoder::closeAllInstances(); + ThunderStub::clear(); + } +}; + +/* getStatus: currentState="ON" → "Enabled" */ +TEST_F(VideoDecoderThunderTest, GetStatus_ON_Enabled) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, true, "ON"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Enabled"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getStatus: currentState="STANDBY" → "X_COMCAST-COM_Standby" */ +TEST_F(VideoDecoderThunderTest, GetStatus_STANDBY) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, true, "STANDBY"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "X_COMCAST-COM_Standby"); +} + +/* getStatus: currentState="LIGHT_SLEEP" also maps to Standby */ +TEST_F(VideoDecoderThunderTest, GetStatus_LIGHT_SLEEP_IsStandby) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, true, "LIGHT_SLEEP"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "X_COMCAST-COM_Standby"); +} + +/* getStatus: currentState="DEEP_SLEEP" also maps to Standby */ +TEST_F(VideoDecoderThunderTest, GetStatus_DEEP_SLEEP_IsStandby) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, true, "DEEP_SLEEP"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "X_COMCAST-COM_Standby"); +} + +/* getStatus: currentState="OFF" → "Disabled" */ +TEST_F(VideoDecoderThunderTest, GetStatus_OFF_Disabled) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, true, "OFF"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Disabled"); +} + +/* getStatus: Thunder failure → NOK, paramValue="Error" */ +TEST_F(VideoDecoderThunderTest, GetStatus_ThunderFailure) +{ + ThunderStub::setString(THUNDER_PM_GET_POWER_STATE, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, NOK); + EXPECT_STREQ(msg.paramValue, "Error"); +} + +/* getName: returns "VideoDecoder" */ +TEST_F(VideoDecoderThunderTest, GetName_Format) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg); + + EXPECT_EQ(rc, OK); + /* dev_id=1 instance is "VideoDecoderHDMI0" in buildPortNameHash */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::StartsWith("VideoDecoder")); +} + +/* getEnable: always returns true */ +TEST_F(VideoDecoderThunderTest, GetEnable_AlwaysTrue) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* handleSetMsg: always NOT_HANDLED */ +TEST_F(VideoDecoderThunderTest, HandleSetMsg_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleSetMsg("Enable", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("Status", &msg), NOT_HANDLED); +} + +/* getContentAspectRatio: Thunder returns "16:9" */ +TEST_F(VideoDecoderThunderTest, GetContentAspectRatio_Success) +{ + ThunderStub::setString(THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, true, "16:9"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ContentAspectRatio", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "16:9"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getContentAspectRatio: Thunder failure → falls back to "16:9", still returns OK */ +TEST_F(VideoDecoderThunderTest, GetContentAspectRatio_ThunderFailure_FallsBackTo16_9) +{ + ThunderStub::setString(THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ContentAspectRatio", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "16:9"); +} + +/* getX_RDKCENTRAL-COM_MPEGHPart2: returns the capabilities path string */ +TEST_F(VideoDecoderThunderTest, GetHEVC_ReturnsProfilePath) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_RDKCENTRAL-COM_MPEGHPart2", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* Value should be the HEVC_PROFILE_PATH constant from the implementation */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("MPEGHPart2")); +} + +/* ==================================================================== + * Additional AudioOutput Tests (missing getters) + * ==================================================================== */ + +/* getAudioLevel: calls getVolumeLevel → returns numeric level as UnsignedInt */ +TEST_F(AudioOutputThunderTest, GetAudioLevel_Success) +{ + ThunderStub::setInt(THUNDER_DS_GET_VOLUME_LEVEL, true, 75); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("AudioLevel", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_UnsignedIntType); + EXPECT_EQ(get_uint(msg.paramValue), 75u); +} + +/* getAudioLevel: Thunder failure → NOK */ +TEST_F(AudioOutputThunderTest, GetAudioLevel_ThunderFailure) +{ + ThunderStub::setInt(THUNDER_DS_GET_VOLUME_LEVEL, false, 0); + + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("AudioLevel", &msg), NOK); +} + +/* getX_COMCAST_COM_AudioOptimalLevel: hardcoded "0.000000", no Thunder call */ +TEST_F(AudioOutputThunderTest, GetAudioOptimalLevel_HardcodedZero) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_AudioOptimalLevel", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "0.000000"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* ==================================================================== + * Additional DisplayDevice Tests (missing getters) + * ==================================================================== */ + +/* getSupportedResolutions: Thunder returns "720p,1080p60" → formatted CSV */ +TEST_F(DisplayDeviceThunderTest, GetSupportedResolutions_Success) +{ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_RESOLUTIONS, true, "720p,1080p60"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("SupportedResolutions", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* "720p" → "1280x720p/59.94Hz", "1080p60" → "1920x1080p/59.94Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1280x720p/59.94Hz")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1920x1080p/59.94Hz")); +} + +/* getSupportedResolutions: Thunder failure → NOK */ +TEST_F(DisplayDeviceThunderTest, GetSupportedResolutions_ThunderFailure) +{ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_RESOLUTIONS, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("SupportedResolutions", &msg), NOK); +} + +/* getPreferredResolution: Thunder returns "1080p60" → TR-181 format */ +TEST_F(DisplayDeviceThunderTest, GetPreferredResolution_Success) +{ + ThunderStub::setString(THUNDER_DS_GET_DEFAULT_RESOLUTION, true, "1080p60"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("PreferredResolution", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "1920x1080p/59.94Hz"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getPreferredResolution: Thunder failure → NOK */ +TEST_F(DisplayDeviceThunderTest, GetPreferredResolution_ThunderFailure) +{ + ThunderStub::setString(THUNDER_DS_GET_DEFAULT_RESOLUTION, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("PreferredResolution", &msg), NOK); +} + +/* getX_COMCAST_COM_EDID (via "EEDID"): empty EDID → empty paramValue, returns OK */ +TEST_F(DisplayDeviceThunderTest, GetEEDID_NotConnected_EmptyValue) +{ + /* Stub returns empty string for EDID — display not connected */ + ThunderStub::setString(THUNDER_DS_READ_EDID, true, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("EEDID", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramValue[0], '\0'); +} + +/* getX_COMCAST_COM_EDID (via "X_COMCAST-COM_EDID"): same as above */ +TEST_F(DisplayDeviceThunderTest, GetComcastEDID_NotConnected_EmptyValue) +{ + ThunderStub::setString(THUNDER_DS_READ_EDID, true, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("X_COMCAST-COM_EDID", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramValue[0], '\0'); +} + +/* ==================================================================== + * Capabilities Tests + * ==================================================================== */ + +class CapabilitiesThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceCapabilities *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + m_iface = hostIf_STBServiceCapabilities::getInstance(); + ASSERT_NE(m_iface, nullptr); + } + + void TearDown() override + { + hostIf_STBServiceCapabilities::closeInstance(m_iface); + m_iface = nullptr; + ThunderStub::clear(); + } + + /** Helper: build a HOSTIF_MsgData_t with paramName set to the given path. */ + static HOSTIF_MsgData_t makeMsgWithPath(const char *paramName) + { + HOSTIF_MsgData_t m; + memset(&m, 0, sizeof(m)); + strncpy(m.paramName, paramName, TR69HOSTIFMGR_MAX_PARAM_LEN - 1); + return m; + } +}; + +/* getVideoStandards: Thunder returns JSON containing "HEVC" and "H264" */ +TEST_F(CapabilitiesThunderTest, GetVideoStandards_HEVC_and_H264) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"supportedFormats\":[\"HEVC\",\"H264\"],\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.VideoStandards"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_THAT(std::string(msg.paramValue), + ::testing::HasSubstr("MPEGH-Part2")); + EXPECT_THAT(std::string(msg.paramValue), + ::testing::HasSubstr("MPEG4-Part10")); +} + +/* getVideoStandards: Thunder returns MPEG2 only */ +TEST_F(CapabilitiesThunderTest, GetVideoStandards_MPEG2Only) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"supportedFormats\":[\"MPEG2\"],\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.VideoStandards"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("MPEG2-Part2")); +} + +/* getVideoStandards: Thunder failure → NOK */ +TEST_F(CapabilitiesThunderTest, GetVideoStandards_ThunderFailure) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS, false, ""); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.VideoStandards"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, NOK); +} + +/* getNumHEVCProfileEntries: Thunder returns 1 entry */ +TEST_F(CapabilitiesThunderTest, GetNumHEVCProfileEntries_Success) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"numberOfEntries\":1," + "\"entries\":[{\"profile\":\"MAIN 10\",\"level\":5.1}]," + "\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevelNumberOfEntries"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_UnsignedIntType); + EXPECT_EQ(*reinterpret_cast(msg.paramValue), 1u); +} + +/* getNumHEVCProfileEntries: Thunder failure → NOK */ +TEST_F(CapabilitiesThunderTest, GetNumHEVCProfileEntries_ThunderFailure) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, false, ""); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevelNumberOfEntries"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, NOK); +} + +/* getHEVCProfileDetails: reads Profile name for ProfileLevel.1.Profile */ +TEST_F(CapabilitiesThunderTest, GetHEVCProfileDetails_ProfileName) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"numberOfEntries\":1," + "\"entries\":[{\"profile\":\"MAIN 10\",\"level\":5.1}]," + "\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Profile"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "MAIN 10"); +} + +/* getHEVCProfileDetails: reads Level for ProfileLevel.1.Level */ +TEST_F(CapabilitiesThunderTest, GetHEVCProfileDetails_Level) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"numberOfEntries\":1," + "\"entries\":[{\"profile\":\"MAIN 10\",\"level\":5.1}]," + "\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Level"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* Level is formatted as "5.1" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("5.1")); +} + +/* getSupportedResolutions (HDMI): Thunder returns comma-delimited codes */ +TEST_F(CapabilitiesThunderTest, GetSupportedResolutions_Success) +{ + /* getSupportedResolutions uses invokeThunderPluginMethod (raw JSON path) */ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS, true, + "{\"supportedSettopResolutions\":[\"720p\",\"1080p60\"],\"success\":true}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.HDMI.SupportedResolutions"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* getTR181ResolutionString converts "720p"→"1280x720p/59.94Hz", "1080p60"→"1920x1080p/59.94Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1280x720p/59.94Hz")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1920x1080p/59.94Hz")); +} + +/* getSupportedResolutions (HDMI): Thunder failure → NOK */ +TEST_F(CapabilitiesThunderTest, GetSupportedResolutions_ThunderFailure) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS, false, ""); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.HDMI.SupportedResolutions"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, NOK); +} + +/* handleSetMsg: always NOT_HANDLED */ +TEST_F(CapabilitiesThunderTest, HandleSetMsg_AlwaysNotHandled) +{ + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.VideoStandards"); + EXPECT_EQ(m_iface->handleSetMsg(&msg), NOT_HANDLED); +} + +/* Unknown path under Capabilities → NOK (invalid parameter name) */ +TEST_F(CapabilitiesThunderTest, HandleGetMsg_UnknownPath_NOK) +{ + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.UnknownParam"); + int rc = m_iface->handleGetMsg(&msg); + EXPECT_EQ(rc, NOK); +} + +/* getVideoStandards: multiple formats */ +TEST_F(CapabilitiesThunderTest, GetVideoStandards_MultipleFormats) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_VIDEO_CODING_FORMATS, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"supportedFormats\":[\"HEVC\",\"H264\",\"MPEG2\",\"VP9\"],\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.VideoDecoder.VideoStandards"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + /* Should contain all mapped formats */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("MPEGH-Part2")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("MPEG4-Part10")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("MPEG2-Part2")); +} + +/* getNumHEVCProfileEntries: multiple entries */ +TEST_F(CapabilitiesThunderTest, GetNumHEVCProfileEntries_MultipleEntries) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"numberOfEntries\":3," + "\"entries\":[" + "{\"profile\":\"MAIN\",\"level\":4.0}," + "{\"profile\":\"MAIN 10\",\"level\":5.0}," + "{\"profile\":\"MAIN 10\",\"level\":5.1}" + "],\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevelNumberOfEntries"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(*reinterpret_cast(msg.paramValue), 3u); +} + +/* getHEVCProfileDetails: access second entry */ +TEST_F(CapabilitiesThunderTest, GetHEVCProfileDetails_SecondEntry) +{ + ThunderStub::setRaw(THUNDER_DS_GET_VIDEO_CODEC_INFO, true, + "{\"jsonrpc\":\"2.0\",\"id\":\"3\",\"result\":" + "{\"numberOfEntries\":2," + "\"entries\":[" + "{\"profile\":\"MAIN\",\"level\":4.0}," + "{\"profile\":\"MAIN 10\",\"level\":5.1}" + "],\"success\":true}}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities." + "VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.2.Profile"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "MAIN 10"); +} + +/* getSupportedResolutions: empty list */ +TEST_F(CapabilitiesThunderTest, GetSupportedResolutions_EmptyList) +{ + ThunderStub::setRaw(THUNDER_DS_GET_SUPPORTED_SETTOP_RESOLUTIONS, true, + "{\"supportedSettopResolutions\":[],\"success\":true}"); + + HOSTIF_MsgData_t msg = makeMsgWithPath( + "Device.Services.STBService.1.Capabilities.HDMI.SupportedResolutions"); + int rc = m_iface->handleGetMsg(&msg); + + EXPECT_EQ(rc, OK); + /* Should return empty or minimal string */ + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* ==================================================================== + * VideoOutput Tests + * ==================================================================== */ + +class VideoOutputThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceVideoOutput *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + hostIf_STBServiceVideoOutput::closeAllInstances(); + + /* buildPortNameHash() queries getSupportedVideoDisplays. + * Return "HDMI0" so one instance (dev_id=1, portName="HDMI0") is created. */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, true, "HDMI0"); + + m_iface = hostIf_STBServiceVideoOutput::getInstance(1); + ASSERT_NE(m_iface, nullptr) << "getInstance returned nullptr"; + } + + void TearDown() override + { + hostIf_STBServiceVideoOutput::closeAllInstances(); + ThunderStub::clear(); + } +}; + +/* getStatus: DisplayInfo reports connected → "Enabled" */ +TEST_F(VideoOutputThunderTest, GetStatus_Connected_Enabled) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Enabled"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getStatus: DisplayInfo reports not connected → "Disabled" */ +TEST_F(VideoOutputThunderTest, GetStatus_NotConnected_Disabled) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Disabled"); +} + +/* getStatus: Thunder failure → connected=false → "Disabled", still OK */ +TEST_F(VideoOutputThunderTest, GetStatus_ThunderFailure_Disabled) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Disabled"); +} + +/* getEnable: always returns OK with boolean true */ +TEST_F(VideoOutputThunderTest, GetEnable_AlwaysTrue) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* getDisplayFormat: Thunder returns current resolution string */ +TEST_F(VideoOutputThunderTest, GetDisplayFormat_Success) +{ + ThunderStub::setString(THUNDER_DS_GET_CURRENT_RESOLUTION, true, "1080p60"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("DisplayFormat", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "1080p60"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getDisplayFormat: Thunder failure → NOK */ +TEST_F(VideoOutputThunderTest, GetDisplayFormat_ThunderFailure) +{ + ThunderStub::setString(THUNDER_DS_GET_CURRENT_RESOLUTION, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("DisplayFormat", &msg), NOK); +} + +/* getVideoFormat: Thunder returns aspect ratio string */ +TEST_F(VideoOutputThunderTest, GetVideoFormat_Success) +{ + ThunderStub::setString(THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, true, "16:9"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("VideoFormat", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "16:9"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getVideoFormat: Thunder failure → falls back to "Unknown", still OK */ +TEST_F(VideoOutputThunderTest, GetVideoFormat_ThunderFailure_FallsBackToUnknown) +{ + ThunderStub::setString(THUNDER_DS_GET_DISPLAY_ASPECT_RATIO, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("VideoFormat", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Unknown"); +} + +/* getAspectRatioBehaviour: Thunder returns zoom mode */ +TEST_F(VideoOutputThunderTest, GetAspectRatioBehaviour_Success) +{ + ThunderStub::setString(THUNDER_AVO_GET_ZOOM_MODE, true, "FULL"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("AspectRatioBehaviour", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "FULL"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getAspectRatioBehaviour: Thunder failure → falls back to "None", still OK */ +TEST_F(VideoOutputThunderTest, GetAspectRatioBehaviour_ThunderFailure_FallsBackToNone) +{ + ThunderStub::setString(THUNDER_AVO_GET_ZOOM_MODE, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("AspectRatioBehaviour", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "None"); +} + +/* getHDCP: HDCP compliant → boolean true */ +TEST_F(VideoOutputThunderTest, GetHDCP_Compliant) +{ + ThunderStub::setBool(THUNDER_HDCP_GET_STATUS, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("HDCP", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* getHDCP: HDCP not compliant → boolean false */ +TEST_F(VideoOutputThunderTest, GetHDCP_NotCompliant) +{ + ThunderStub::setBool(THUNDER_HDCP_GET_STATUS, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("HDCP", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramValue[0], '0'); +} + +/* getHDCP: Thunder failure → defaults to not-compliant (false), still OK */ +TEST_F(VideoOutputThunderTest, GetHDCP_ThunderFailure_DefaultsToFalse) +{ + ThunderStub::setBool(THUNDER_HDCP_GET_STATUS, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("HDCP", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramValue[0], '0'); +} + +/* getName: returns the port name set at construction */ +TEST_F(VideoOutputThunderTest, GetName_ReturnsPortName) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "HDMI0"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* handleSetMsg: always NOT_HANDLED */ +TEST_F(VideoOutputThunderTest, HandleSetMsg_AlwaysNotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleSetMsg("Status", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("Enable", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("DisplayFormat", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("AspectRatioBehaviour", &msg), NOT_HANDLED); + EXPECT_EQ(m_iface->handleSetMsg("HDCP", &msg), NOT_HANDLED); +} + +/* handleGetMsg: unknown parameter → NOT_HANDLED */ +TEST_F(VideoOutputThunderTest, HandleGetMsg_UnknownParam_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("X_UnknownParam", &msg), NOT_HANDLED); +} + +/* ==================================================================== + * HDMI Tests + * ==================================================================== */ + +class HDMIThunderTest : public ::testing::Test +{ +protected: + hostIf_STBServiceHDMI *m_iface = nullptr; + + void SetUp() override + { + ThunderStub::clear(); + hostIf_STBServiceHDMI::closeAllInstances(); + + /* buildPortNameHash() queries getSupportedVideoDisplays. + * Return "HDMI0" so one instance (dev_id=1, portName="HDMI0") is created. */ + ThunderStub::setString(THUNDER_DS_GET_SUPPORTED_VIDEO_DISPLAYS, true, "HDMI0"); + + m_iface = hostIf_STBServiceHDMI::getInstance(1); + ASSERT_NE(m_iface, nullptr) << "getInstance returned nullptr"; + } + + void TearDown() override + { + hostIf_STBServiceHDMI::closeAllInstances(); + ThunderStub::clear(); + } +}; + +/* getEnable: port enabled → boolean true */ +TEST_F(HDMIThunderTest, GetEnable_Enabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '1'); +} + +/* getEnable: port disabled → boolean false */ +TEST_F(HDMIThunderTest, GetEnable_Disabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_BooleanType); + EXPECT_EQ(msg.paramValue[0], '0'); +} + +/* getEnable: Thunder failure → NOK */ +TEST_F(HDMIThunderTest, GetEnable_ThunderFailure) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, false, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg); + + EXPECT_EQ(rc, NOK); +} + +/* getStatus: port enabled → "Enabled" */ +TEST_F(HDMIThunderTest, GetStatus_Enabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Enabled"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getStatus: port disabled → "Disabled" */ +TEST_F(HDMIThunderTest, GetStatus_Disabled) +{ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, false); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Disabled"); +} + +/* getName: returns the port name */ +TEST_F(HDMIThunderTest, GetName_ReturnsPortName) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "HDMI0"); + EXPECT_EQ(msg.paramtype, hostIf_StringType); +} + +/* getResolutionValue: Thunder returns resolution details */ +TEST_F(HDMIThunderTest, GetResolutionValue_Success) +{ + /* Mock getCurrentResolution to return full JSON with w, h, progressive */ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"1080p60\",\"w\":1920,\"h\":1080,\"progressive\":true,\"success\":true}"); + + /* Mock framerate query to return "Framerate6000" (60.00 Hz) */ + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate6000"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* Expected format: "1920x1080p/60Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1920x1080p")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("60Hz")); +} + +/* getResolutionValue: non-integer framerate (59.94) */ +TEST_F(HDMIThunderTest, GetResolutionValue_DecimalFramerate) +{ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"1080p\",\"w\":1920,\"h\":1080,\"progressive\":true,\"success\":true}"); + + /* Mock framerate "Framerate5994" → 59.94 Hz */ + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate5994"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + /* Expected format: "1920x1080p/59.94Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("59.94Hz")); +} + +/* getResolutionValue: interlaced format */ +TEST_F(HDMIThunderTest, GetResolutionValue_Interlaced) +{ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"1080i50\",\"w\":1920,\"h\":1080,\"progressive\":false,\"success\":true}"); + + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate5000"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + /* Expected format: "1920x1080i/50Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("1920x1080i")); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("50Hz")); +} + +/* getResolutionValue: Thunder failure → NOK */ +TEST_F(HDMIThunderTest, GetResolutionValue_ThunderFailure) +{ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, NOK); +} + +/* getHDMIResolutionMode: returns static member value */ +TEST_F(HDMIThunderTest, GetResolutionMode_ReturnsStaticValue) +{ + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionMode", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg.paramtype, hostIf_StringType); + /* Default is "Manual" defined in Components_HDMI.h (HDMI_RESOLUTION_MODE_MANUAL) */ + EXPECT_STREQ(msg.paramValue, "Manual"); +} + +/* handleSetMsg: all setters return NOT_HANDLED in Thunder build */ +TEST_F(HDMIThunderTest, HandleSetMsg_AllReturnsNotHandled) +{ + const char *setParams[] = { + "ResolutionMode", "ResolutionValue", "Enable" + }; + HOSTIF_MsgData_t msg = makeMsg(); + for (const char *p : setParams) + { + int rc = m_iface->handleSetMsg(p, &msg); + EXPECT_EQ(rc, NOT_HANDLED) << "Expected NOT_HANDLED for setter: " << p; + } +} + +/* handleGetMsg: unknown parameter → NOT_HANDLED */ +TEST_F(HDMIThunderTest, HandleGetMsg_UnknownParam_NotHandled) +{ + HOSTIF_MsgData_t msg = makeMsg(); + EXPECT_EQ(m_iface->handleGetMsg("X_UnknownParam", &msg), NOT_HANDLED); +} + +/* DisplayDevice sub-object: forwards to DisplayDevice handler */ +TEST_F(HDMIThunderTest, GetDisplayDevice_Status_ForwardedToSubObject) +{ + ThunderStub::setBool(THUNDER_DI_CONNECTED, true, true); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("DisplayDevice.Status", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg.paramValue, "Present"); +} + +/* getResolutionValue: framerate fallback to parsing resolution string */ +TEST_F(HDMIThunderTest, GetResolutionValue_FramerateFallback) +{ + /* DisplayInfo.framerate fails, fall back to parsing resolution string */ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"1080p60\",\"w\":1920,\"h\":1080,\"progressive\":true,\"success\":true}"); + + /* DisplayInfo.framerate fails */ + ThunderStub::setString(THUNDER_DI_FRAMERATE, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + /* Should parse "60" from "1080p60" → "1920x1080p/60Hz" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("60Hz")); +} + +/* getResolutionValue: no framerate data available */ +TEST_F(HDMIThunderTest, GetResolutionValue_NoFramerateData) +{ + /* Resolution without framerate indicator */ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"720p\",\"w\":1280,\"h\":720,\"progressive\":true,\"success\":true}"); + + ThunderStub::setString(THUNDER_DI_FRAMERATE, false, ""); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + /* Should return resolution without framerate: "1280x720p" */ + EXPECT_THAT(std::string(msg.paramValue), ::testing::StartsWith("1280x720p")); +} + +/* getResolutionValue: 4K resolution */ +TEST_F(HDMIThunderTest, GetResolutionValue_4K) +{ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"2160p60\",\"w\":3840,\"h\":2160,\"progressive\":true,\"success\":true}"); + + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate6000"); + + HOSTIF_MsgData_t msg = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg); + + EXPECT_EQ(rc, OK); + EXPECT_THAT(std::string(msg.paramValue), ::testing::HasSubstr("3840x2160p/60Hz")); +} + +/* getEnable with pChanged: detects change */ +TEST_F(HDMIThunderTest, GetEnable_WithChangeDetection) +{ + /* First call: enabled=true */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, true); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Enable", &msg1); + + /* Second call: enabled=false (changed) */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, false); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Enable", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_EQ(msg2.paramValue[0], '0'); +} + +/* getStatus with pChanged: detects change */ +TEST_F(HDMIThunderTest, GetStatus_WithChangeDetection) +{ + /* First call: Enabled */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, true); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Status", &msg1); + + /* Second call: Disabled (changed) */ + ThunderStub::setBool(THUNDER_DS_GET_ENABLE_VIDEO_PORT, true, false); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Status", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "Disabled"); +} + +/* getName with pChanged: detects no change */ +TEST_F(HDMIThunderTest, GetName_NoChange) +{ + /* First call */ + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("Name", &msg1); + + /* Second call: name doesn't change */ + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("Name", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_STREQ(msg2.paramValue, "HDMI0"); +} + +/* getResolutionValue with pChanged: detects change */ +TEST_F(HDMIThunderTest, GetResolutionValue_WithChangeDetection) +{ + /* First call: 1080p60 */ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"1080p60\",\"w\":1920,\"h\":1080,\"progressive\":true,\"success\":true}"); + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate6000"); + HOSTIF_MsgData_t msg1 = makeMsg(); + m_iface->handleGetMsg("ResolutionValue", &msg1); + + /* Second call: 720p (changed) */ + ThunderStub::setRaw(THUNDER_DS_GET_CURRENT_RESOLUTION, true, + "{\"resolution\":\"720p\",\"w\":1280,\"h\":720,\"progressive\":true,\"success\":true}"); + ThunderStub::setString(THUNDER_DI_FRAMERATE, true, "Framerate5994"); + HOSTIF_MsgData_t msg2 = makeMsg(); + int rc = m_iface->handleGetMsg("ResolutionValue", &msg2); + + EXPECT_EQ(rc, OK); + EXPECT_THAT(std::string(msg2.paramValue), ::testing::HasSubstr("1280x720p")); +} + +/* ==================================================================== + * main + * ==================================================================== */ +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/hostif/profiles/STBService/gtest/thunder_plugin_stub.cpp b/src/hostif/profiles/STBService/gtest/thunder_plugin_stub.cpp new file mode 100644 index 000000000..0749d9bb1 --- /dev/null +++ b/src/hostif/profiles/STBService/gtest/thunder_plugin_stub.cpp @@ -0,0 +1,307 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2024 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 thunder_plugin_stub.cpp + * + * Stub implementations of invokeThunderPlugin* helper functions used by the + * STBService Thunder components. Tests configure canned responses via the + * ThunderStub namespace before exercising the production code under test. + * + * Also provides stub implementations of: + * - put_boolean / get_boolean (normally in hostIf_utils.cpp) + * - getStringValue (normally in hostIf_utils.cpp) + * - IARM_Bus_BroadcastEvent (no-op, only needed for doUpdates path) + */ + +#include +#include +#include +#include "hostIf_utils.h" +#include "hostIf_main.h" + +/* ------------------------------------------------------------------ */ +/* Per-test response store */ +/* ------------------------------------------------------------------ */ + +namespace ThunderStub { + +struct BoolResp { bool success; bool value; }; +struct StringResp { bool success; std::string value; }; +struct IntResp { bool success; int value; }; +struct ULongResp { bool success; unsigned long value; }; +struct RawResp { bool success; std::string response; }; + +static std::map g_bool; +static std::map g_string; +static std::map g_int; +static std::map g_ulong; +static std::map g_raw; + +/** Set the bool result returned when the given Thunder method is invoked. */ +void setBool(const std::string& method, bool success, bool value = false) +{ + g_bool[method] = {success, value}; +} + +/** Set the string result returned when the given Thunder method is invoked. */ +void setString(const std::string& method, bool success, const std::string& value = "") +{ + g_string[method] = {success, value}; +} + +/** Set the int result returned when the given Thunder method is invoked. */ +void setInt(const std::string& method, bool success, int value = 0) +{ + g_int[method] = {success, value}; +} + +/** Set the unsigned long result returned when the given Thunder method is invoked. */ +void setULong(const std::string& method, bool success, unsigned long value = 0UL) +{ + g_ulong[method] = {success, value}; +} + +/** + * Set a raw JSON response for methods that use invokeThunderPluginMethod directly + * (e.g. Capabilities getVideoStandards, getNumHEVCProfileEntries). + */ +void setRaw(const std::string& method, bool success, const std::string& response = "") +{ + g_raw[method] = {success, response}; +} + +/** Clear all configured responses (call from test SetUp). */ +void clear() +{ + g_bool.clear(); + g_string.clear(); + g_int.clear(); + g_ulong.clear(); + g_raw.clear(); +} + +} /* namespace ThunderStub */ + +/* ------------------------------------------------------------------ */ +/* invokeThunderPlugin* stub implementations */ +/* ------------------------------------------------------------------ */ + +bool invokeThunderPluginMethod(const std::string& method, + const std::string& /*paramsJson*/, + std::string& response) +{ + auto it = ThunderStub::g_raw.find(method); + if (it == ThunderStub::g_raw.end() || !it->second.success) + return false; + response = it->second.response; + return true; +} + +bool invokeThunderPluginMethodAndExtractBoolField(const std::string& method, + const std::string& /*paramsJson*/, const std::string& /*fieldName*/, bool& value) +{ + auto it = ThunderStub::g_bool.find(method); + if (it == ThunderStub::g_bool.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractStringField(const std::string& method, + const std::string& /*paramsJson*/, const std::string& /*fieldName*/, std::string& value) +{ + auto it = ThunderStub::g_string.find(method); + if (it == ThunderStub::g_string.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractNumberField(const std::string& method, + const std::string& /*paramsJson*/, const std::string& /*fieldName*/, int& value) +{ + auto it = ThunderStub::g_int.find(method); + if (it == ThunderStub::g_int.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractULongField(const std::string& method, + const std::string& /*paramsJson*/, const std::string& /*fieldName*/, unsigned long& value) +{ + auto it = ThunderStub::g_ulong.find(method); + if (it == ThunderStub::g_ulong.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractDelimitedStringArrayField( + const std::string& method, const std::string& /*paramsJson*/, + const std::string& /*fieldName*/, const std::string& /*delimiter*/, + std::string& value) +{ + auto it = ThunderStub::g_string.find(method); + if (it == ThunderStub::g_string.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractScalarStringResult(const std::string& method, + const std::string& /*paramsJson*/, std::string& value) +{ + auto it = ThunderStub::g_string.find(method); + if (it == ThunderStub::g_string.end()) + return false; + value = it->second.value; + return it->second.success; +} + +bool invokeThunderPluginMethodAndExtractScalarBoolResult(const std::string& method, + const std::string& /*paramsJson*/, bool& value) +{ + auto it = ThunderStub::g_bool.find(method); + if (it == ThunderStub::g_bool.end()) + return false; + value = it->second.value; + return it->second.success; +} + +/* ------------------------------------------------------------------ */ +/* hostIf_utils helpers (normally in hostIf_utils.cpp) */ +/* ------------------------------------------------------------------ */ + +bool get_boolean(const char *ptr) +{ + return ptr && (*ptr != '\0') && (*ptr != '0'); +} + +void put_boolean(char *ptr, bool val) +{ + if (ptr) + *ptr = val ? '1' : '0'; +} + +int get_int(const char *ptr) +{ + if (!ptr) return 0; + return *reinterpret_cast(ptr); +} + +void put_int(char *ptr, int val) +{ + if (ptr) + *reinterpret_cast(ptr) = val; +} + +uint get_uint(char *ptr) +{ + if (!ptr) return 0u; + return *reinterpret_cast(ptr); +} + +void put_uint(char *ptr, uint val) +{ + if (ptr) + *reinterpret_cast(ptr) = val; +} + +int get_ulong(const char *ptr) +{ + if (!ptr) return 0; + return static_cast(*reinterpret_cast(ptr)); +} + +void put_ulong(char *ptr, unsigned long val) +{ + if (ptr) + *reinterpret_cast(ptr) = val; +} + +std::string getStringValue(HOSTIF_MsgData_t *stMsgData) +{ + if (!stMsgData) + return ""; + return std::string(stMsgData->paramValue, + static_cast(stMsgData->paramLen)); +} + +void putValue(HOSTIF_MsgData_t *stMsgData, const std::string &value) +{ + if (!stMsgData) + return; + strncpy(stMsgData->paramValue, value.c_str(), TR69HOSTIFMGR_MAX_PARAM_LEN - 1); + stMsgData->paramValue[TR69HOSTIFMGR_MAX_PARAM_LEN - 1] = '\0'; + stMsgData->paramtype = hostIf_StringType; + stMsgData->paramLen = static_cast(value.size()); +} + +/* ------------------------------------------------------------------ */ +/* matchComponent stub (normally in hostIf_utils.cpp) */ +/* ------------------------------------------------------------------ */ + +#include +#include + +#define MAX_NUM_LEN 10 + +bool matchComponent(const char* pParam, const char *pKey, const char **pSetting, int &instanceNo) +{ + if (!pParam || !pKey || !pSetting) return false; + int str_len = static_cast(strlen(pKey)); + bool ret = (strncasecmp(pParam, pKey, str_len) == 0); + if (ret) + { + const char *tmp_ptr; + int tmp_len; + if ((pParam[str_len] == '.') && + (tmp_ptr = strchr(pParam + str_len + 1, '.')) && + (tmp_len = static_cast(tmp_ptr - (pParam + str_len + 1))) < MAX_NUM_LEN) + { + char tmp_buff[MAX_NUM_LEN]; + memset(tmp_buff, 0, sizeof(tmp_buff)); + strncpy(tmp_buff, pParam + str_len + 1, tmp_len); + instanceNo = atoi(tmp_buff); + *pSetting = tmp_ptr + 1; + } + else + { + instanceNo = 0; + *pSetting = pParam + str_len; + } + } + return ret; +} + +/* ------------------------------------------------------------------ */ +/* IARM stub (no-op) */ +/* ------------------------------------------------------------------ */ + +#include "libIBus.h" + +IARM_Result_t IARM_Bus_BroadcastEvent(const char * /*ownerName*/, + IARM_EventId_t /*eventId*/, + void * /*arg*/, + size_t /*argLen*/) +{ + return IARM_RESULT_SUCCESS; +} diff --git a/src/hostif/src/hostIf_utils.cpp b/src/hostif/src/hostIf_utils.cpp index 9bd7377a8..df4a040e1 100644 --- a/src/hostif/src/hostIf_utils.cpp +++ b/src/hostif/src/hostIf_utils.cpp @@ -1126,6 +1126,35 @@ bool invokeThunderPluginMethodAndExtractScalarStringResult(const std::string& me return ok; } +bool invokeThunderPluginMethodAndExtractScalarBoolResult(const std::string& method, + const std::string& paramsJson, bool& value) +{ + value = false; + + std::string response; + if (!invokeThunderPluginMethod(method, paramsJson, response)) { + return false; + } + + cJSON* root = cJSON_Parse(response.c_str()); + if (root == NULL) { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "%s: json parse error for method %s\n", __FUNCTION__, method.c_str()); + return false; + } + + cJSON* resultObj = cJSON_GetObjectItem(root, "result"); + bool ok = false; + if (cJSON_IsBool(resultObj)) { + value = cJSON_IsTrue(resultObj); + ok = true; + } else { + RDK_LOG(RDK_LOG_ERROR, LOG_TR69HOSTIF, "%s: Missing/invalid scalar bool result for method %s\n", __FUNCTION__, method.c_str()); + } + + cJSON_Delete(root); + return ok; +} + #ifdef GTEST_ENABLE size_t (*getWriteCurlResponse(void))(void *ptr, size_t size, size_t nmemb, std::string stream) { return &writeCurlResponse; diff --git a/test/functional-tests/tests/tr69hostif_stbservice_thunder.py b/test/functional-tests/tests/tr69hostif_stbservice_thunder.py new file mode 100644 index 000000000..69b8ae3d2 --- /dev/null +++ b/test/functional-tests/tests/tr69hostif_stbservice_thunder.py @@ -0,0 +1,674 @@ +################################################################################ +# If not stated otherwise in this file or this component's Licenses file the +# following copyright and licenses apply: +# +# Copyright 2024 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. +################################################################################ + +""" +L2 functional tests for Thunder-migrated STBService TR-069 parameters. + +These tests exercise the tr69hostif daemon over rbus/TR-181 and verify that: + 1. Thunder-backed GET parameters return a valid response (no rbus exception). + 2. Thunder-backed SET parameters that are NOT_HANDLED return an rbus error. + 3. Key Thunder plugin log messages are emitted (curl response code 200). + +Base TR-069 path: + Device.Services.STBService.1.Components... +""" + +import pytest + +from helper_functions import * + +# ───────────────────────────────────────────────────────────────────────────── +# Constants +# ───────────────────────────────────────────────────────────────────────────── + +STBSVC_BASE = "Device.Services.STBService.1" + +AUDIO_BASE = STBSVC_BASE + ".Components.AudioOutput.1" +SPDIF_BASE = STBSVC_BASE + ".Components.SPDIF.1" +DISPDEV_BASE = STBSVC_BASE + ".Components.DisplayDevice.1" +VIDDEC_BASE = STBSVC_BASE + ".Components.VideoDecoder.1" +VIDOUT_BASE = STBSVC_BASE + ".Components.VideoOutput.1" +CAPS_BASE = STBSVC_BASE + ".Capabilities" + +CURL_OK_MSG = "curl response : 0 http response code: 200" + +# ───────────────────────────────────────────────────────────────────────────── +# Per-test fixture: clear the tr69hostif log before every test so that +# grep_tr69hostiflogs() assertions only match entries produced by the +# current test and are not satisfied by log lines from previous tests. +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.fixture(autouse=True) +def clear_log_before_test(): + clear_tr69hostiflogs() + yield + +# ───────────────────────────────────────────────────────────────────────────── +# AudioOutput – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=310) +def test_STBService_AudioOutput_Get_Status(): + """ + GET AudioOutput.1.Status should return one of Enabled / Muted / Disabled + and must produce a successful Thunder curl call in the tr69hostif log. + """ + param = AUDIO_BASE + ".Status" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG), \ + "Expected Thunder curl log entry for getEnableAudioPort" + # Status must be one of the valid TR-135 values + valid = {"Enabled", "Muted", "Disabled"} + assert any(v in rstdout for v in valid), \ + f"Unexpected Status value: {rstdout}" + + +@pytest.mark.run(order=311) +def test_STBService_AudioOutput_Get_Enable(): + """ + GET AudioOutput.1.Enable – always returns true (port is in the list). + No Thunder call is made for this parameter. + """ + param = AUDIO_BASE + ".Enable" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "true" in rstdout.lower() or "1" in rstdout + + +@pytest.mark.run(order=312) +def test_STBService_AudioOutput_Get_CancelMute(): + """ + GET AudioOutput.1.CancelMute – boolean, backed by Thunder getMuted call. + """ + param = AUDIO_BASE + ".CancelMute" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=313) +def test_STBService_AudioOutput_Get_Name(): + """ + GET AudioOutput.1.Name – returns "AudioOutputPort". + No Thunder call required. + """ + param = AUDIO_BASE + ".Name" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "AudioOutputPort" in rstdout + + +@pytest.mark.run(order=314) +def test_STBService_AudioOutput_Get_AudioFormat(): + """ + GET AudioOutput.1.AudioFormat – backed by Thunder getAudioFormat. + """ + param = AUDIO_BASE + ".AudioFormat" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=315) +def test_STBService_AudioOutput_Get_AudioStereoMode(): + """ + GET AudioOutput.1.X_COMCAST-COM_AudioStereoMode – backed by getSoundMode. + """ + param = AUDIO_BASE + ".X_COMCAST-COM_AudioStereoMode" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=316) +def test_STBService_AudioOutput_Get_AudioCompression(): + """ + GET AudioOutput.1.X_COMCAST-COM_AudioCompression – backed by getMS12AudioCompression. + """ + param = AUDIO_BASE + ".X_COMCAST-COM_AudioCompression" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=317) +def test_STBService_AudioOutput_Get_AudioEncoding(): + """ + GET AudioOutput.1.X_COMCAST-COM_AudioEncoding – backed by getAudioEncoding. + """ + param = AUDIO_BASE + ".X_COMCAST-COM_AudioEncoding" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +# ───────────────────────────────────────────────────────────────────────────── +# AudioOutput – SET tests (NOT_HANDLED in Thunder build) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=318) +def test_STBService_AudioOutput_Set_CancelMute_NotHandled(): + """ + SET AudioOutput.1.CancelMute must fail – setter is NOT_HANDLED in + the Thunder build. + """ + param = AUDIO_BASE + ".CancelMute" + rstdout = rbus_set_data(param, "boolean", "true") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +@pytest.mark.run(order=319) +def test_STBService_AudioOutput_Set_AudioLevel_NotHandled(): + """ + SET AudioOutput.1.AudioLevel must fail. + """ + param = AUDIO_BASE + ".AudioLevel" + rstdout = rbus_set_data(param, "uint", "50") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +# ───────────────────────────────────────────────────────────────────────────── +# SPDIF – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=321) +def test_STBService_SPDIF_Get_Status_NotHandled(): + """ + GET SPDIF.1.Status is NOT_HANDLED in Thunder build – rbus exception expected. + """ + param = SPDIF_BASE + ".Status" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING in rstdout, \ + f"Expected rbus exception for NOT_HANDLED param {param}" + + +@pytest.mark.run(order=322) +def test_STBService_SPDIF_Get_Enable_NotHandled(): + """ + GET SPDIF.1.Enable is NOT_HANDLED in Thunder build – rbus exception expected. + """ + param = SPDIF_BASE + ".Enable" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING in rstdout, \ + f"Expected rbus exception for NOT_HANDLED param {param}" + + +# ───────────────────────────────────────────────────────────────────────────── +# DisplayDevice – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=323) +def test_STBService_DisplayDevice_Get_Status(): + """ + GET DisplayDevice.1.Status – backed by Thunder DisplayInfo.1.connected. + Returns "Present" or "Absent". + """ + param = DISPDEV_BASE + ".Status" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() in ("Present", "Absent"), \ + f"Unexpected DisplayDevice Status: {rstdout}" + + +@pytest.mark.run(order=324) +def test_STBService_DisplayDevice_Get_SupportedResolutions(): + """ + GET DisplayDevice.1.SupportedResolutions – backed by getSupportedResolutions. + """ + param = DISPDEV_BASE + ".SupportedResolutions" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=325) +def test_STBService_DisplayDevice_Get_PreferredResolution(): + """ + GET DisplayDevice.1.PreferredResolution – backed by getDefaultResolution. + """ + param = DISPDEV_BASE + ".PreferredResolution" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=326) +def test_STBService_DisplayDevice_Set_Status_NotHandled(): + """ + SET DisplayDevice.1.Status must fail – no setter in Thunder build. + """ + param = DISPDEV_BASE + ".Status" + rstdout = rbus_set_data(param, "string", "Present") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +# ───────────────────────────────────────────────────────────────────────────── +# VideoDecoder – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=327) +def test_STBService_VideoDecoder_Get_Status(): + """ + GET VideoDecoder.1.Status – backed by org.rdk.PowerManager.GetPowerState. + Returns one of Enabled / Disabled / X_COMCAST-COM_Standby. + """ + param = VIDDEC_BASE + ".Status" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + valid = {"Enabled", "Disabled", "X_COMCAST-COM_Standby", "Error"} + assert any(v in rstdout for v in valid), \ + f"Unexpected VideoDecoder Status: {rstdout}" + + +@pytest.mark.run(order=328) +def test_STBService_VideoDecoder_Get_Enable(): + """ + GET VideoDecoder.1.Enable – always true (decoder present). + """ + param = VIDDEC_BASE + ".Enable" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "true" in rstdout.lower() or "1" in rstdout + + +@pytest.mark.run(order=329) +def test_STBService_VideoDecoder_Get_Name(): + """ + GET VideoDecoder.1.Name – returns "VideoDecoder". + """ + param = VIDDEC_BASE + ".Name" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "VideoDecoder" in rstdout + + +@pytest.mark.run(order=330) +def test_STBService_VideoDecoder_Set_Status_NotHandled(): + """ + SET VideoDecoder.1.Status must fail – setter is NOT_HANDLED. + """ + param = VIDDEC_BASE + ".Status" + rstdout = rbus_set_data(param, "string", "Enabled") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +# ───────────────────────────────────────────────────────────────────────────── +# AudioOutput – additional GET tests (missing from first pass) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=331) +def test_STBService_AudioOutput_Get_AudioLevel(): + """ + GET AudioOutput.1.AudioLevel – backed by getEnableAudioPort + getMuted, + returns one of Enabled / Muted / Disabled. + """ + param = AUDIO_BASE + ".AudioLevel" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + # AudioLevel is a numeric volume level (0-100) + assert rstdout.strip().isdigit() or rstdout.strip().lstrip('-').isdigit(), \ + f"Expected numeric AudioLevel, got: {rstdout}" + + +@pytest.mark.run(order=332) +def test_STBService_AudioOutput_Get_AudioOptimalLevel(): + """ + GET AudioOutput.1.X_COMCAST-COM_AudioOptimalLevel – hardcoded "0.000000", + no Thunder call required. + """ + param = AUDIO_BASE + ".X_COMCAST-COM_AudioOptimalLevel" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "0.000000" in rstdout + + +# ───────────────────────────────────────────────────────────────────────────── +# DisplayDevice – additional GET tests (missing from first pass) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=333) +def test_STBService_DisplayDevice_Get_EEDID(): + """ + GET DisplayDevice.1.EEDID – backed by readEDID; empty when no display connected. + """ + param = DISPDEV_BASE + ".EEDID" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +@pytest.mark.run(order=334) +def test_STBService_DisplayDevice_Get_X_COMCAST_EDID(): + """ + GET DisplayDevice.1.X_COMCAST-COM_EDID – same backend as EEDID. + """ + param = DISPDEV_BASE + ".X_COMCAST-COM_EDID" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + + +# ───────────────────────────────────────────────────────────────────────────── +# VideoDecoder – additional GET tests (missing from first pass) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=335) +def test_STBService_VideoDecoder_Get_ContentAspectRatio(): + """ + GET VideoDecoder.1.ContentAspectRatio – backed by getDisplayAspectRatio; + falls back to "16:9" if Thunder fails. + """ + param = VIDDEC_BASE + ".ContentAspectRatio" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + # Aspect ratio is always returned (fallback "16:9" on failure) + assert rstdout.strip() != "" + + +# ───────────────────────────────────────────────────────────────────────────── +# VideoOutput – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=336) +def test_STBService_VideoOutput_Get_Status(): + """ + GET VideoOutput.1.Status – backed by Thunder DisplayInfo.1.connected. + Returns "Enabled" when display is connected, "Disabled" otherwise. + """ + param = VIDOUT_BASE + ".Status" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() in ("Enabled", "Disabled"), \ + f"Unexpected VideoOutput Status: {rstdout}" + + +@pytest.mark.run(order=337) +def test_STBService_VideoOutput_Get_Enable(): + """ + GET VideoOutput.1.Enable – always true (port is present in the list). + No Thunder call required. + """ + param = VIDOUT_BASE + ".Enable" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert "true" in rstdout.lower() or "1" in rstdout + + +@pytest.mark.run(order=338) +def test_STBService_VideoOutput_Get_DisplayFormat(): + """ + GET VideoOutput.1.DisplayFormat – backed by getCurrentResolution. + Returns the current resolution string (e.g. "1080p60"). + """ + param = VIDOUT_BASE + ".DisplayFormat" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() != "" + + +@pytest.mark.run(order=339) +def test_STBService_VideoOutput_Get_VideoFormat(): + """ + GET VideoOutput.1.VideoFormat – backed by getDisplayAspectRatio; + falls back to "Unknown" if Thunder fails. + """ + param = VIDOUT_BASE + ".VideoFormat" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + # Always returns something (fallback "Unknown") + assert rstdout.strip() != "" + + +@pytest.mark.run(order=340) +def test_STBService_VideoOutput_Get_AspectRatioBehaviour(): + """ + GET VideoOutput.1.AspectRatioBehaviour – backed by AVOutput.getZoomMode; + falls back to "None" if Thunder fails. + """ + param = VIDOUT_BASE + ".AspectRatioBehaviour" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert rstdout.strip() != "" + + +@pytest.mark.run(order=341) +def test_STBService_VideoOutput_Get_HDCP(): + """ + GET VideoOutput.1.HDCP – backed by HdcpProfile.getHDCPStatus; boolean. + """ + param = VIDOUT_BASE + ".HDCP" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert "true" in rstdout.lower() or "false" in rstdout.lower() \ + or "1" in rstdout or "0" in rstdout + + +@pytest.mark.run(order=342) +def test_STBService_VideoOutput_Get_Name(): + """ + GET VideoOutput.1.Name – returns the port name (e.g. "HDMI0"). + No Thunder call required. + """ + param = VIDOUT_BASE + ".Name" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert rstdout.strip() != "" + + +# ───────────────────────────────────────────────────────────────────────────── +# VideoOutput – SET tests (NOT_HANDLED) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=343) +def test_STBService_VideoOutput_Set_DisplayFormat_NotHandled(): + """ + SET VideoOutput.1.DisplayFormat must fail – all setters are NOT_HANDLED. + """ + param = VIDOUT_BASE + ".DisplayFormat" + rstdout = rbus_set_data(param, "string", "1080p60") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +@pytest.mark.run(order=344) +def test_STBService_VideoOutput_Set_HDCP_NotHandled(): + """ + SET VideoOutput.1.HDCP must fail – setter is NOT_HANDLED. + """ + param = VIDOUT_BASE + ".HDCP" + rstdout = rbus_set_data(param, "boolean", "true") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}" + + +# ───────────────────────────────────────────────────────────────────────────── +# Capabilities – GET tests +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=345) +def test_STBService_Capabilities_Get_VideoStandards(): + """ + GET Capabilities.VideoDecoder.VideoStandards – backed by + getSupportedVideoCodingFormats; returns TR-135 formatted codec strings. + """ + param = CAPS_BASE + ".VideoDecoder.VideoStandards" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + # Must contain at least one known codec format + known = {"MPEGH-Part2", "MPEG4-Part10", "MPEG2-Part2"} + assert any(k in rstdout for k in known), \ + f"Unexpected VideoStandards value: {rstdout}" + + +@pytest.mark.run(order=346) +def test_STBService_Capabilities_Get_HEVCProfileEntries(): + """ + GET Capabilities.VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevelNumberOfEntries + – backed by getVideoCodecInfo; returns integer count >= 1. + """ + param = (CAPS_BASE + + ".VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevelNumberOfEntries") + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + # Value should be a positive integer + try: + assert int(rstdout.strip()) >= 1 + except ValueError: + assert False, f"Expected integer, got: {rstdout}" + + +@pytest.mark.run(order=347) +def test_STBService_Capabilities_Get_HEVCProfileLevel_Profile(): + """ + GET Capabilities.VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Profile + – reads the first HEVC profile name from getVideoCodecInfo. + """ + param = (CAPS_BASE + + ".VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Profile") + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() != "" + + +@pytest.mark.run(order=348) +def test_STBService_Capabilities_Get_HEVCProfileLevel_Level(): + """ + GET Capabilities.VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Level + – reads the first HEVC level string from getVideoCodecInfo. + """ + param = (CAPS_BASE + + ".VideoDecoder.X_RDKCENTRAL-COM_MPEGHPart2.ProfileLevel.1.Level") + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() != "" + + +@pytest.mark.run(order=349) +def test_STBService_Capabilities_Get_HDMI_SupportedResolutions(): + """ + GET Capabilities.HDMI.SupportedResolutions – backed by + getSupportedSettopResolutions; returns comma-separated resolution codes. + """ + param = CAPS_BASE + ".HDMI.SupportedResolutions" + rstdout = rbus_get_data(param) + + assert RBUS_EXCEPTION_STRING not in rstdout, \ + f"rbus exception getting {param}" + assert CURL_OK_MSG in grep_tr69hostiflogs(CURL_OK_MSG) + assert rstdout.strip() != "" + + +# ───────────────────────────────────────────────────────────────────────────── +# Capabilities – SET test (NOT_HANDLED) +# ───────────────────────────────────────────────────────────────────────────── + +@pytest.mark.run(order=350) +def test_STBService_Capabilities_Set_VideoStandards_NotHandled(): + """ + SET Capabilities.VideoDecoder.VideoStandards must fail – + handleSetMsg always returns NOT_HANDLED. + """ + param = CAPS_BASE + ".VideoDecoder.VideoStandards" + rstdout = rbus_set_data(param, "string", "MPEG4-Part10 ([ISO/IEC14496-10])") + + assert RBUS_SUCCESS_STRING not in rstdout, \ + f"Expected SET to fail for NOT_HANDLED param {param}"