From 8b59f4d31e86c9808721413c8507c15a51ecd696 Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Thu, 22 Dec 2022 02:15:48 +0800 Subject: [PATCH] [posix] add power calibration support (#8293) The actual output power of the Thread device may be determined by both the Thread radio chip and the FEM. Consider the output power error of the Thread radio chip and the gain error of the FEM, the actual output power is inconsistent with the expected output power. To guarantee that the actual output power is accurate and meet the regulatory requirements, the output power should be calibrated in the factory if the output power is not accurate. This commit contains the following changes: - Adds a power calibration implementation - Adds a config file to configure the target power for different countries - Adds a tool for the factory to persist the power calibration data --- .github/workflows/posix.yml | 4 + Android.mk | 41 +++ CMakeLists.txt | 1 + .../openthread-core-simulation-config.h | 20 ++ include/openthread/instance.h | 2 +- include/openthread/platform/radio.h | 94 +++++ src/core/BUILD.gn | 4 + src/core/CMakeLists.txt | 2 + src/core/Makefile.am | 4 + src/core/common/instance.cpp | 3 + src/core/common/instance.hpp | 8 + src/core/config/platform.h | 10 + src/core/config/power_calibration.h | 68 ++++ src/core/openthread-core-config.h | 1 + src/core/utils/power_calibration.cpp | 214 ++++++++++++ src/core/utils/power_calibration.hpp | 168 +++++++++ src/lib/spinel/radio_spinel.hpp | 49 +++ src/lib/spinel/radio_spinel_impl.hpp | 73 ++++ src/lib/spinel/spinel.c | 2 + src/lib/spinel/spinel.h | 24 +- src/ncp/ncp_base.cpp | 38 ++ src/ncp/ncp_base_dispatcher.cpp | 8 +- src/posix/platform/CMakeLists.txt | 3 + src/posix/platform/Makefile.am | 3 + src/posix/platform/config_file.cpp | 215 ++++++++++++ src/posix/platform/config_file.hpp | 107 ++++++ .../platform/openthread-core-posix-config.h | 10 + src/posix/platform/openthread-posix-config.h | 23 ++ src/posix/platform/openthread.conf.example | 24 ++ src/posix/platform/power.cpp | 126 +++++++ src/posix/platform/power.hpp | 289 +++++++++++++++ src/posix/platform/power_updater.cpp | 180 ++++++++++ src/posix/platform/power_updater.hpp | 116 ++++++ src/posix/platform/radio.cpp | 40 ++- tests/scripts/expect/ot-fct.exp | 48 +++ tests/unit/CMakeLists.txt | 21 ++ tests/unit/test_power_calibration.cpp | 151 ++++++++ tools/CMakeLists.txt | 31 ++ tools/ot-fct/CMakeLists.txt | 49 +++ tools/ot-fct/README.md | 75 ++++ tools/ot-fct/cli.cpp | 329 ++++++++++++++++++ tools/ot-fct/cli.hpp | 117 +++++++ tools/ot-fct/logging.cpp | 41 +++ tools/ot-fct/main.cpp | 102 ++++++ 44 files changed, 2934 insertions(+), 4 deletions(-) create mode 100644 src/core/config/power_calibration.h create mode 100644 src/core/utils/power_calibration.cpp create mode 100644 src/core/utils/power_calibration.hpp create mode 100644 src/posix/platform/config_file.cpp create mode 100644 src/posix/platform/config_file.hpp create mode 100644 src/posix/platform/openthread.conf.example create mode 100644 src/posix/platform/power.cpp create mode 100644 src/posix/platform/power.hpp create mode 100644 src/posix/platform/power_updater.cpp create mode 100644 src/posix/platform/power_updater.hpp create mode 100755 tests/scripts/expect/ot-fct.exp create mode 100644 tests/unit/test_power_calibration.cpp create mode 100644 tools/CMakeLists.txt create mode 100644 tools/ot-fct/CMakeLists.txt create mode 100644 tools/ot-fct/README.md create mode 100644 tools/ot-fct/cli.cpp create mode 100644 tools/ot-fct/cli.hpp create mode 100644 tools/ot-fct/logging.cpp create mode 100644 tools/ot-fct/main.cpp diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index fceebd0c8..aa0bc2498 100644 --- a/.github/workflows/posix.yml +++ b/.github/workflows/posix.yml @@ -65,6 +65,10 @@ jobs: ulimit -c unlimited ./script/test prepare_coredump_upload OT_OPTIONS='-DOT_READLINE=OFF -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' VIRTUAL_TIME=0 OT_NODE_TYPE=rcp ./script/test build expect + - name: Run ot-fct + run: | + OT_CMAKE_NINJA_TARGET="ot-fct" script/cmake-build posix + tests/scripts/expect/ot-fct.exp - name: Check Crash if: ${{ failure() }} run: | diff --git a/Android.mk b/Android.mk index b42e7acec..063dafe61 100644 --- a/Android.mk +++ b/Android.mk @@ -373,6 +373,7 @@ LOCAL_SRC_FILES := \ src/core/utils/otns.cpp \ src/core/utils/parse_cmdline.cpp \ src/core/utils/ping_sender.cpp \ + src/core/utils/power_calibration.cpp \ src/core/utils/slaac_address.cpp \ src/core/utils/srp_client_buffers.cpp \ src/lib/hdlc/hdlc.cpp \ @@ -384,6 +385,7 @@ LOCAL_SRC_FILES := \ src/posix/platform/alarm.cpp \ src/posix/platform/backbone.cpp \ src/posix/platform/backtrace.cpp \ + src/posix/platform/config_file.cpp \ src/posix/platform/daemon.cpp \ src/posix/platform/entropy.cpp \ src/posix/platform/firewall.cpp \ @@ -395,6 +397,8 @@ LOCAL_SRC_FILES := \ src/posix/platform/misc.cpp \ src/posix/platform/multicast_routing.cpp \ src/posix/platform/netif.cpp \ + src/posix/platform/power.cpp \ + src/posix/platform/power_updater.cpp \ src/posix/platform/radio.cpp \ src/posix/platform/radio_url.cpp \ src/posix/platform/settings.cpp \ @@ -656,6 +660,43 @@ LOCAL_SRC_FILES := src/posix/client.cpp include $(BUILD_EXECUTABLE) endif # ($(USE_OTBR_DAEMON), 1) +include $(CLEAR_VARS) + +LOCAL_MODULE := ot-fct +LOCAL_MODULE_TAGS := eng + +LOCAL_CPPFLAGS := \ + -std=c++11 \ + -pedantic-errors \ + $(NULL) + +LOCAL_CFLAGS := \ + $(OPENTHREAD_PUBLIC_CFLAGS) \ + $(OPENTHREAD_PRIVATE_CFLAGS) \ + $(OPENTHREAD_PROJECT_CFLAGS) \ + $(NULL) + +LOCAL_C_INCLUDES := \ + $(OPENTHREAD_PROJECT_INCLUDES) \ + $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/src/ \ + $(LOCAL_PATH)/src/core \ + $(LOCAL_PATH)/src/posix/platform \ + $(NULL) + +LOCAL_SRC_FILES := \ + src/core/common/string.cpp \ + src/core/utils/parse_cmdline.cpp \ + src/lib/platform/exit_code.c \ + src/posix/platform/config_file.cpp \ + src/posix/platform/power.cpp \ + tools/ot-fct/cli.cpp \ + tools/ot-fct/logging.cpp \ + tools/ot-fct/main.cpp \ + $(NULL) + +include $(BUILD_EXECUTABLE) + ifneq ($(OPENTHREAD_PROJECT_ANDROID_MK),) include $(OPENTHREAD_PROJECT_ANDROID_MK) endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a31b16d3..84b90d8b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,7 @@ if(OT_PLATFORM STREQUAL "simulation") endif() add_subdirectory(tests) +add_subdirectory(tools) add_custom_target(print-ot-config ALL COMMAND ${CMAKE_COMMAND} diff --git a/examples/platforms/simulation/openthread-core-simulation-config.h b/examples/platforms/simulation/openthread-core-simulation-config.h index f17af97a9..78bd55f16 100644 --- a/examples/platforms/simulation/openthread-core-simulation-config.h +++ b/examples/platforms/simulation/openthread-core-simulation-config.h @@ -265,4 +265,24 @@ #define OPENTHREAD_CONFIG_DETERMINISTIC_ECDSA_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE + * + * Define as 1 to enable power calibration support. + * + */ +#ifndef OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE +#define OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + * + * Define as 1 to enable platform power calibration support. + * + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE 1 +#endif + #endif // OPENTHREAD_CORE_SIMULATION_CONFIG_H_ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 981523bfb..34f294fe1 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (270) +#define OPENTHREAD_API_VERSION (271) /** * @addtogroup api-instance diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index a4b916bba..67a3619d6 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -1144,6 +1144,100 @@ otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, otShortAddress aShortAddress, const otExtAddress *aExtAddress); +/** + * Add a calibrated power of the specified channel to the power calibration table. + * + * @note This API is an optional radio platform API. It's up to the platform layer to implement it. + * + * The @p aActualPower is the actual measured output power when the parameters of the radio hardware modules + * are set to the @p aRawPowerSetting. + * + * The raw power setting is an opaque byte array. OpenThread doesn't define the format of the raw power setting. + * Its format is radio hardware related and it should be defined by the developers in the platform radio driver. + * For example, if the radio hardware contains both the radio chip and the FEM chip, the raw power setting can be + * a combination of the radio power register and the FEM gain value. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aChannel The radio channel. + * @param[in] aActualPower The actual power in 0.01dBm. + * @param[in] aRawPowerSetting A pointer to the raw power setting byte array. + * @param[in] aRawPowerSettingLength The length of the @p aRawPowerSetting. + * + * @retval OT_ERROR_NONE Successfully added the calibrated power to the power calibration table. + * @retval OT_ERROR_NO_BUFS No available entry in the power calibration table. + * @retval OT_ERROR_INVALID_ARGS The @p aChannel, @p aActualPower or @p aRawPowerSetting is invalid or the + * @p aActualPower already exists in the power calibration table. + * @retval OT_ERROR_NOT_IMPLEMENTED This feature is not implemented. + * + */ +otError otPlatRadioAddCalibratedPower(otInstance *aInstance, + uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength); + +/** + * Clear all calibrated powers from the power calibration table. + * + * @note This API is an optional radio platform API. It's up to the platform layer to implement it. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval OT_ERROR_NONE Successfully cleared all calibrated powers from the power calibration table. + * @retval OT_ERROR_NOT_IMPLEMENTED This feature is not implemented. + * + */ +otError otPlatRadioClearCalibratedPowers(otInstance *aInstance); + +/** + * Set the target power for the given channel. + * + * @note This API is an optional radio platform API. It's up to the platform layer to implement it. + * If this API is implemented, the function `otPlatRadioSetTransmitPower()` should be disabled. + * + * The radio driver should set the actual output power to be less than or equal to the target power and as close + * as possible to the target power. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aChannel The radio channel. + * @param[in] aTargetPower The target power in 0.01dBm. Passing `INT16_MAX` will disable this channel to use the + * target power. + * + * @retval OT_ERROR_NONE Successfully set the target power. + * @retval OT_ERROR_INVALID_ARGS The @p aChannel or @p aTargetPower is invalid. + * @retval OT_ERROR_NOT_IMPLEMENTED The feature is not implemented. + * + */ +otError otPlatRadioSetChannelTargetPower(otInstance *aInstance, uint8_t aChannel, int16_t aTargetPower); + +/** + * Get the raw power setting for the given channel. + * + * @note OpenThread `src/core/utils` implements a default implementation of the API `otPlatRadioAddCalibratedPower()`, + * `otPlatRadioClearCalibratedPowers()` and `otPlatRadioSetChannelTargetPower()`. This API is provided by + * the default implementation to get the raw power setting for the given channel. If the platform doesn't + * use the default implementation, it can ignore this API. + * + * Platform radio layer should parse the raw power setting based on the radio layer defined format and set the + * parameters of each radio hardware module. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aChannel The radio channel. + * @param[out] aRawPowerSetting A pointer to the raw power setting byte array. + * @param[in,out] aRawPowerSettingLength On input, a pointer to the size of @p aRawPowerSetting. + * On output, a pointer to the length of the raw power setting data. + * + * @retval OT_ERROR_NONE Successfully got the target power. + * @retval OT_ERROR_INVALID_ARGS The @p aChannel is invalid, @p aRawPowerSetting or @p aRawPowerSettingLength is NULL + * or @aRawPowerSettingLength is too short. + * @retval OT_ERROR_NOT_FOUND The raw power setting for the @p aChannel was not found. + * + */ +extern otError otPlatRadioGetRawPowerSetting(otInstance *aInstance, + uint8_t aChannel, + uint8_t *aRawPowerSetting, + uint16_t *aRawPowerSettingLength); + /** * @} * diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index a6b56eeda..c8ecbc71a 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -706,6 +706,8 @@ openthread_core_files = [ "utils/parse_cmdline.hpp", "utils/ping_sender.cpp", "utils/ping_sender.hpp", + "utils/power_calibration.cpp", + "utils/power_calibration.hpp", "utils/slaac_address.cpp", "utils/slaac_address.hpp", "utils/srp_client_buffers.cpp", @@ -745,6 +747,7 @@ openthread_radio_sources = [ "radio/radio_platform.cpp", "thread/link_quality.cpp", "utils/parse_cmdline.cpp", + "utils/power_calibration.cpp", ] header_pattern = [ @@ -791,6 +794,7 @@ source_set("libopenthread_core_config") { "config/parent_search.h", "config/ping_sender.h", "config/platform.h", + "config/power_calibration.h", "config/radio_link.h", "config/sntp_client.h", "config/srp_client.h", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 719332238..078ab4ed8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -240,6 +240,7 @@ set(COMMON_SOURCES utils/otns.cpp utils/parse_cmdline.cpp utils/ping_sender.cpp + utils/power_calibration.cpp utils/slaac_address.cpp utils/srp_client_buffers.cpp ) @@ -276,6 +277,7 @@ set(RADIO_COMMON_SOURCES radio/radio_platform.cpp thread/link_quality.cpp utils/parse_cmdline.cpp + utils/power_calibration.cpp ) set(OT_VENDOR_EXTENSION "" CACHE STRING "specify a C++ source file built as part of OpenThread core library") diff --git a/src/core/Makefile.am b/src/core/Makefile.am index fb6cd20eb..77f06e893 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -330,6 +330,7 @@ SOURCES_COMMON = \ utils/otns.cpp \ utils/parse_cmdline.cpp \ utils/ping_sender.cpp \ + utils/power_calibration.cpp \ utils/slaac_address.cpp \ utils/srp_client_buffers.cpp \ $(NULL) @@ -366,6 +367,7 @@ RADIO_SOURCES_COMMON = \ radio/radio_platform.cpp \ thread/link_quality.cpp \ utils/parse_cmdline.cpp \ + utils/power_calibration.cpp \ $(NULL) EXTRA_DIST = \ @@ -514,6 +516,7 @@ HEADERS_COMMON = \ config/parent_search.h \ config/ping_sender.h \ config/platform.h \ + config/power_calibration.h \ config/radio_link.h \ config/sntp_client.h \ config/srp_client.h \ @@ -649,6 +652,7 @@ HEADERS_COMMON = \ utils/otns.hpp \ utils/parse_cmdline.hpp \ utils/ping_sender.hpp \ + utils/power_calibration.hpp \ utils/slaac_address.hpp \ utils/srp_client_buffers.hpp \ $(NULL) diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index bcdfd6ddd..e97ea08bd 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -236,6 +236,9 @@ Instance::Instance(void) #endif #if OPENTHREAD_CONFIG_DIAG_ENABLE , mDiags(*this) +#endif +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + , mPowerCalibration(*this) #endif , mIsInitialized(false) { diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index c5e37dbae..6789f454e 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -60,6 +60,7 @@ #include "mac/link_raw.hpp" #include "radio/radio.hpp" #include "utils/otns.hpp" +#include "utils/power_calibration.hpp" #if OPENTHREAD_FTD || OPENTHREAD_MTD #include "backbone_router/backbone_tmf.hpp" @@ -618,6 +619,9 @@ private: #if OPENTHREAD_CONFIG_DIAG_ENABLE FactoryDiags::Diags mDiags; #endif +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + Utils::PowerCalibration mPowerCalibration; +#endif bool mIsInitialized; @@ -960,6 +964,10 @@ template <> inline Extension::ExtensionBase &Instance::Get(void) { return mExten template <> inline FactoryDiags::Diags &Instance::Get(void) { return mDiags; } #endif +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +template <> inline Utils::PowerCalibration &Instance::Get(void) { return mPowerCalibration; } +#endif + /** * @} * diff --git a/src/core/config/platform.h b/src/core/config/platform.h index ca132dbae..9fa475bdf 100644 --- a/src/core/config/platform.h +++ b/src/core/config/platform.h @@ -158,6 +158,16 @@ #define OPENTHREAD_CONFIG_PLATFORM_MAC_KEYS_EXPORTABLE_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + * + * Define as 1 to enable platform power calibration support. + * + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE 0 +#endif + #if OPENTHREAD_CONFIG_PLATFORM_RADIO_PROPRIETARY_SUPPORT #if (!defined(OPENTHREAD_CONFIG_PLATFORM_RADIO_PROPRIETARY_CHANNEL_PAGE) || \ !defined(OPENTHREAD_CONFIG_PLATFORM_RADIO_PROPRIETARY_CHANNEL_MIN) || \ diff --git a/src/core/config/power_calibration.h b/src/core/config/power_calibration.h new file mode 100644 index 000000000..56e9467e2 --- /dev/null +++ b/src/core/config/power_calibration.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes compile-time configurations for power calibration module. + * + */ + +#ifndef CONFIG_POWER_CALIBRATION_H_ +#define CONFIG_POWER_CALIBRATION_H_ + +/** + * @def OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE + * + * Define as 1 to enable power calibration support. + * + */ +#ifndef OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE +#define OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE + * + * The size of the raw power setting byte array. + * + */ +#ifndef OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE +#define OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE 16 +#endif + +/** + * @def OPENTHREAD_CONFIG_POWER_CALIBRATION_NUM_CALIBRATED_POWER_ENTRIES + * + * The number of the calibrated power entries for each channel. + * + */ +#ifndef OPENTHREAD_CONFIG_POWER_CALIBRATION_NUM_CALIBRATED_POWER_ENTRIES +#define OPENTHREAD_CONFIG_POWER_CALIBRATION_NUM_CALIBRATED_POWER_ENTRIES 6 +#endif + +#endif // CONFIG_POWER_CALIBRATION_H_ diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index 00d114975..1a52d6e3b 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -89,6 +89,7 @@ #include "config/parent_search.h" #include "config/ping_sender.h" #include "config/platform.h" +#include "config/power_calibration.h" #include "config/radio_link.h" #include "config/sntp_client.h" #include "config/srp_client.h" diff --git a/src/core/utils/power_calibration.cpp b/src/core/utils/power_calibration.cpp new file mode 100644 index 000000000..b23bdcf0d --- /dev/null +++ b/src/core/utils/power_calibration.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "power_calibration.hpp" + +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +#include "common/as_core_type.hpp" +#include "common/code_utils.hpp" +#include "common/locator_getters.hpp" + +namespace ot { +namespace Utils { + +PowerCalibration::PowerCalibration(Instance &aInstance) + : InstanceLocator(aInstance) + , mLastChannel(0) + , mCalibratedPowerIndex(kInvalidIndex) +{ + for (int16_t &targetPower : mTargetPowerTable) + { + targetPower = kInvalidPower; + } +} + +void PowerCalibration::CalibratedPowerEntry::Init(int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + AssertPointerIsNotNull(aRawPowerSetting); + OT_ASSERT(aRawPowerSettingLength <= kMaxRawPowerSettingSize); + + mActualPower = aActualPower; + mLength = aRawPowerSettingLength; + memcpy(mSettings, aRawPowerSetting, aRawPowerSettingLength); +} + +Error PowerCalibration::CalibratedPowerEntry::GetRawPowerSetting(uint8_t *aRawPowerSetting, + uint16_t *aRawPowerSettingLength) +{ + Error error = kErrorNone; + + AssertPointerIsNotNull(aRawPowerSetting); + AssertPointerIsNotNull(aRawPowerSettingLength); + VerifyOrExit(*aRawPowerSettingLength >= mLength, error = kErrorInvalidArgs); + + memcpy(aRawPowerSetting, mSettings, mLength); + *aRawPowerSettingLength = mLength; + +exit: + return error; +} + +Error PowerCalibration::AddCalibratedPower(uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + Error error = kErrorNone; + CalibratedPowerEntry entry; + uint8_t chIndex; + + AssertPointerIsNotNull(aRawPowerSetting); + VerifyOrExit(IsChannelValid(aChannel) && aRawPowerSettingLength <= CalibratedPowerEntry::kMaxRawPowerSettingSize, + error = kErrorInvalidArgs); + + chIndex = aChannel - Radio::kChannelMin; + VerifyOrExit(!mCalibratedPowerTables[chIndex].ContainsMatching(aActualPower), error = kErrorInvalidArgs); + VerifyOrExit(!mCalibratedPowerTables[chIndex].IsFull(), error = kErrorNoBufs); + + entry.Init(aActualPower, aRawPowerSetting, aRawPowerSettingLength); + SuccessOrExit(error = mCalibratedPowerTables[chIndex].PushBack(entry)); + + if (aChannel == mLastChannel) + { + mCalibratedPowerIndex = kInvalidIndex; + } + +exit: + return error; +} + +void PowerCalibration::ClearCalibratedPowers(void) +{ + for (CalibratedPowerTable &table : mCalibratedPowerTables) + { + table.Clear(); + } + + mCalibratedPowerIndex = kInvalidIndex; +} + +Error PowerCalibration::SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower) +{ + Error error = kErrorNone; + + VerifyOrExit(IsChannelValid(aChannel), error = kErrorInvalidArgs); + mTargetPowerTable[aChannel - Radio::kChannelMin] = aTargetPower; + + if (aChannel == mLastChannel) + { + mCalibratedPowerIndex = kInvalidIndex; + } + +exit: + return error; +} + +Error PowerCalibration::GetRawPowerSetting(uint8_t aChannel, + uint8_t *aRawPowerSetting, + uint16_t *aRawPowerSettingLength) +{ + Error error = kErrorNone; + uint8_t chIndex; + uint8_t powerIndex = kInvalidIndex; + int16_t foundPower = kInvalidPower; + int16_t targetPower; + int16_t actualPower; + + VerifyOrExit(IsChannelValid(aChannel), error = kErrorInvalidArgs); + VerifyOrExit((mLastChannel != aChannel) || IsPowerUpdated()); + + chIndex = aChannel - Radio::kChannelMin; + targetPower = mTargetPowerTable[chIndex]; + VerifyOrExit(targetPower != kInvalidPower, error = kErrorNotFound); + + for (uint8_t i = 0; i < mCalibratedPowerTables[chIndex].GetLength(); i++) + { + actualPower = mCalibratedPowerTables[chIndex][i].GetActualPower(); + + if ((actualPower <= targetPower) && ((foundPower == kInvalidPower) || (foundPower <= actualPower))) + { + foundPower = actualPower; + powerIndex = i; + } + } + + VerifyOrExit(powerIndex != kInvalidIndex, error = kErrorNotFound); + + mCalibratedPowerIndex = powerIndex; + mLastChannel = aChannel; + +exit: + if (error == kErrorNone) + { + error = mCalibratedPowerTables[mLastChannel - Radio::kChannelMin][mCalibratedPowerIndex].GetRawPowerSetting( + aRawPowerSetting, aRawPowerSettingLength); + } + + return error; +} +} // namespace Utils +} // namespace ot + +using namespace ot; + +otError otPlatRadioAddCalibratedPower(otInstance *aInstance, + uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + return AsCoreType(aInstance).Get().AddCalibratedPower( + aChannel, aActualPower, aRawPowerSetting, aRawPowerSettingLength); +} + +otError otPlatRadioClearCalibratedPowers(otInstance *aInstance) +{ + AsCoreType(aInstance).Get().ClearCalibratedPowers(); + return OT_ERROR_NONE; +} + +otError otPlatRadioSetChannelTargetPower(otInstance *aInstance, uint8_t aChannel, int16_t aTargetPower) +{ + return AsCoreType(aInstance).Get().SetChannelTargetPower(aChannel, aTargetPower); +} + +otError otPlatRadioGetRawPowerSetting(otInstance *aInstance, + uint8_t aChannel, + uint8_t *aRawPowerSetting, + uint16_t *aRawPowerSettingLength) +{ + AssertPointerIsNotNull(aRawPowerSetting); + AssertPointerIsNotNull(aRawPowerSettingLength); + + return AsCoreType(aInstance).Get().GetRawPowerSetting(aChannel, aRawPowerSetting, + aRawPowerSettingLength); +} +#endif // OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE diff --git a/src/core/utils/power_calibration.hpp b/src/core/utils/power_calibration.hpp new file mode 100644 index 000000000..7b1afa25f --- /dev/null +++ b/src/core/utils/power_calibration.hpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes definitions for the platform power calibration module. + * + */ +#ifndef POWER_CALIBRATION_HPP_ +#define POWER_CALIBRATION_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +#include + +#include "common/array.hpp" +#include "common/numeric_limits.hpp" +#include "radio/radio.hpp" + +namespace ot { +namespace Utils { + +/** + * This class implements power calibration module. + * + * The power calibration module implements the radio platform power calibration APIs. It mainly stores the calibrated + * power table and the target power table, provides an API for the platform to get the raw power setting of the + * specified channel. + * + */ +class PowerCalibration : public InstanceLocator, private NonCopyable +{ +public: + explicit PowerCalibration(Instance &aInstance); + + /** + * Add a calibrated power of the specificed channel to the power calibration table. + * + * @param[in] aChannel The radio channel. + * @param[in] aActualPower The actual power in 0.01dBm. + * @param[in] aRawPowerSetting A pointer to the raw power setting byte array. + * @param[in] aRawPowerSettingLength The length of the @p aRawPowerSetting. + * + * @retval kErrorNone Successfully added the calibrated power to the power calibration table. + * @retval kErrorNoBufs No available entry in the power calibration table. + * @retval kErrorInvalidArgs The @p aChannel, @p aActualPower or @p aRawPowerSetting is invalid or the + * @ aActualPower already exists in the power calibration table. + * + */ + Error AddCalibratedPower(uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength); + + /** + * Clear all calibrated powers from the power calibration table. + * + */ + void ClearCalibratedPowers(void); + + /** + * Set the target power for the given channel. + * + * @param[in] aChannel The radio channel. + * @param[in] aTargetPower The target power in 0.01dBm. Passing `INT16_MAX` will disable this channel. + * + * @retval kErrorNone Successfully set the target power. + * @retval kErrorInvalidArgs The @p aChannel or @p aTargetPower is invalid. + * + */ + Error SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower); + + /** + * Get the raw power setting for the given channel. + * + * Platform radio layer should parse the raw power setting based on the radio layer defined format and set the + * parameters of each radio hardware module. + * + * @param[in] aChannel The radio channel. + * @param[out] aRawPowerSetting A pointer to the raw power setting byte array. + * @param[in,out] aRawPowerSettingLength On input, a pointer to the size of @p aRawPowerSetting. + * On output, a pointer to the length of the raw power setting data. + * + * @retval kErrorNone Successfully got the target power. + * @retval kErrorInvalidArgs The @p aChannel is invalid or @p aRawPowerSetting is nullptr. + * @retval kErrorNotFound The raw power setting for the @p aChannel was not found. + * + */ + Error GetRawPowerSetting(uint8_t aChannel, uint8_t *aRawPowerSetting, uint16_t *aRawPowerSettingLength); + +private: + class CalibratedPowerEntry + { + public: + static constexpr uint16_t kMaxRawPowerSettingSize = OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE; + + CalibratedPowerEntry(void) + : mActualPower(kInvalidPower) + , mLength(0) + { + } + + void Init(int16_t aActualPower, const uint8_t *aRawPowerSetting, uint16_t aRawPowerSettingLength); + Error GetRawPowerSetting(uint8_t *aRawPowerSetting, uint16_t *aRawPowerSettingLength); + int16_t GetActualPower(void) const { return mActualPower; } + bool Matches(int16_t aActualPower) const { return aActualPower == mActualPower; } + + private: + int16_t mActualPower; + uint8_t mSettings[kMaxRawPowerSettingSize]; + uint16_t mLength; + }; + + bool IsPowerUpdated(void) const { return mCalibratedPowerIndex == kInvalidIndex; } + bool IsChannelValid(uint8_t aChannel) const + { + return ((aChannel >= Radio::kChannelMin) && (aChannel <= Radio::kChannelMax)); + } + + static constexpr uint8_t kInvalidIndex = NumericLimits::kMax; + static constexpr uint16_t kInvalidPower = NumericLimits::kMax; + static constexpr uint16_t kMaxNumCalibratedPowers = + OPENTHREAD_CONFIG_POWER_CALIBRATION_NUM_CALIBRATED_POWER_ENTRIES; + static constexpr uint16_t kNumChannels = Radio::kChannelMax - Radio::kChannelMin + 1; + + static_assert(kMaxNumCalibratedPowers < NumericLimits::kMax, + "kMaxNumCalibratedPowers is larger than or equal to max"); + + typedef Array CalibratedPowerTable; + + uint8_t mLastChannel; + int16_t mTargetPowerTable[kNumChannels]; + uint8_t mCalibratedPowerIndex; + CalibratedPowerTable mCalibratedPowerTables[kNumChannels]; +}; +} // namespace Utils +} // namespace ot + +#endif // OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#endif // POWER_CALIBRATION_HPP_ diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index bc4d63397..dc3118c61 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -886,6 +886,55 @@ public: */ const otRadioSpinelMetrics *GetRadioSpinelMetrics(void) const { return &mRadioSpinelMetrics; } +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + /** + * Add a calibrated power of the specificed channel to the power calibration table. + * + * @param[in] aChannel The radio channel. + * @param[in] aActualPower The actual power in 0.01dBm. + * @param[in] aRawPowerSetting A pointer to the raw power setting byte array. + * @param[in] aRawPowerSettingLength The length of the @p aRawPowerSetting. + * + * @retval OT_ERROR_NONE Successfully added the calibrated power to the power calibration table. + * @retval OT_ERROR_NO_BUFS No available entry in the power calibration table. + * @retval OT_ERROR_INVALID_ARGS The @p aChannel, @p aActualPower or @p aRawPowerSetting is invalid. + * @retval OT_ERROR_NOT_IMPLEMENTED This feature is not implemented. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * + */ + otError AddCalibratedPower(uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength); + + /** + * Clear all calibrated powers from the power calibration table. + * + * @retval OT_ERROR_NONE Successfully cleared all calibrated powers from the power calibration table. + * @retval OT_ERROR_NOT_IMPLEMENTED This feature is not implemented. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * + */ + otError ClearCalibratedPowers(void); + + /** + * Set the target power for the given channel. + * + * @param[in] aChannel The radio channel. + * @param[in] aTargetPower The target power in 0.01dBm. Passing `INT16_MAX` will disable this channel. + * + * @retval OT_ERROR_NONE Successfully set the target power. + * @retval OT_ERROR_INVALID_ARGS The @p aChannel or @p aTargetPower is invalid.. + * @retval OT_ERROR_NOT_IMPLEMENTED The feature is not implemented. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * + */ + otError SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower); +#endif + private: enum { diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index 0952453a4..567809afb 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -2516,6 +2516,43 @@ uint8_t RadioSpinel::GetCslUncertainty(void) } #endif +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +template +otError RadioSpinel::AddCalibratedPower(uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + otError error; + + assert(aRawPowerSetting != nullptr); + SuccessOrExit(error = Insert(SPINEL_PROP_PHY_CALIBRATED_POWER, + SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S SPINEL_DATATYPE_DATA_WLEN_S, aChannel, + aActualPower, aRawPowerSetting, aRawPowerSettingLength)); + +exit: + return error; +} + +template +otError RadioSpinel::ClearCalibratedPowers(void) +{ + return Set(SPINEL_PROP_PHY_CALIBRATED_POWER, nullptr); +} + +template +otError RadioSpinel::SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower) +{ + otError error = OT_ERROR_NONE; + VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS); + error = + Set(SPINEL_PROP_PHY_CHAN_TARGET_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S, aChannel, aTargetPower); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + template uint32_t RadioSpinel::Snprintf(char *aDest, uint32_t aSize, const char *aFormat, ...) { @@ -3090,6 +3127,42 @@ void RadioSpinel::LogSpinelFrame(const uint8_ m8[2], m8[3], m8[4], m8[5], m8[6], m8[7], flags); } break; + + case SPINEL_PROP_PHY_CALIBRATED_POWER: + { + if (cmd == SPINEL_CMD_PROP_VALUE_INSERT) + { + uint8_t channel; + int16_t actualPower; + uint8_t *rawPowerSetting; + unsigned int rawPowerSettingLength; + + unpacked = spinel_datatype_unpack( + data, len, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S SPINEL_DATATYPE_DATA_WLEN_S, &channel, + &actualPower, &rawPowerSetting, &rawPowerSettingLength); + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + + start += Snprintf(start, static_cast(end - start), + ", ch:%u, actualPower:%d, rawPowerSetting:", channel, actualPower); + for (uint16_t i = 0; i < rawPowerSettingLength; i++) + { + start += Snprintf(start, static_cast(end - start), "%02x", rawPowerSetting[i]); + } + } + } + break; + + case SPINEL_PROP_PHY_CHAN_TARGET_POWER: + { + uint8_t channel; + int16_t targetPower; + + unpacked = + spinel_datatype_unpack(data, len, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S, &channel, &targetPower); + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + start += Snprintf(start, static_cast(end - start), ", ch:%u, targetPower:%d", channel, targetPower); + } + break; } exit: diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index dc4eb5eaf..72f440d76 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1236,6 +1236,8 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) {SPINEL_PROP_PHY_CHAN_PREFERRED, "PHY_CHAN_PREFERRED"}, {SPINEL_PROP_PHY_CHAN_MAX_POWER, "PHY_CHAN_MAX_POWER"}, {SPINEL_PROP_PHY_REGION_CODE, "PHY_REGION_CODE"}, + {SPINEL_PROP_PHY_CALIBRATED_POWER, "PHY_CALIBRATED_POWER"}, + {SPINEL_PROP_PHY_CHAN_TARGET_POWER, "PHY_CHAN_TARGET_POWER"}, {SPINEL_PROP_JAM_DETECT_ENABLE, "JAM_DETECT_ENABLE"}, {SPINEL_PROP_JAM_DETECTED, "JAM_DETECTED"}, {SPINEL_PROP_JAM_DETECT_RSSI_THRESHOLD, "JAM_DETECT_RSSI_THRESHOLD"}, diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 2e70e7da8..a096a7f70 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -417,7 +417,7 @@ * Please see section "Spinel definition compatibility guideline" for more details. * */ -#define SPINEL_RCP_API_VERSION 6 +#define SPINEL_RCP_API_VERSION 7 /** * @def SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION @@ -1743,6 +1743,28 @@ enum */ SPINEL_PROP_PHY_REGION_CODE = SPINEL_PROP_PHY__BEGIN + 12, + /// Calibrated Power Table + /** Format: `A(Csd)` - Insert/Set + * + * The `Insert` command on the property inserts a calibration power entry to the calibrated power table. + * The `Set` command on the property with empty payload clears the calibrated power table. + * + * Structure Parameters: + * `C`: Channel. + * `s`: Actual power in 0.01 dBm. + * `d`: Raw power setting. + */ + SPINEL_PROP_PHY_CALIBRATED_POWER = SPINEL_PROP_PHY__BEGIN + 13, + + /// Target power for a channel + /** Format: `t(Cs)` - Write only + * + * Structure Parameters: + * `C`: Channel. + * `s`: Target power in 0.01 dBm. + */ + SPINEL_PROP_PHY_CHAN_TARGET_POWER = SPINEL_PROP_PHY__BEGIN + 14, + SPINEL_PROP_PHY__END = 0x30, SPINEL_PROP_PHY_EXT__BEGIN = 0x1200, diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 16ab57b3a..576740d40 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -2566,6 +2566,44 @@ exit: } #endif +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error; + uint8_t channel; + int16_t targetPower; + + SuccessOrExit(error = mDecoder.ReadUint8(channel)); + SuccessOrExit(error = mDecoder.ReadInt16(targetPower)); + error = otPlatRadioSetChannelTargetPower(mInstance, channel, targetPower); + +exit: + return error; +} + +template <> otError NcpBase::HandlePropertyInsert(void) +{ + otError error; + uint8_t channel; + int16_t actualPower; + const uint8_t *dataPtr; + uint16_t dataLen; + + SuccessOrExit(error = mDecoder.ReadUint8(channel)); + SuccessOrExit(error = mDecoder.ReadInt16(actualPower)); + SuccessOrExit(error = mDecoder.ReadDataWithLen(dataPtr, dataLen)); + error = otPlatRadioAddCalibratedPower(mInstance, channel, actualPower, dataPtr, dataLen); + +exit: + return error; +} + +template <> otError NcpBase::HandlePropertySet(void) +{ + return otPlatRadioClearCalibratedPowers(mInstance); +} +#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + } // namespace Ncp } // namespace ot diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 89b1f61fe..973e0595e 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -419,6 +419,10 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_FEM_LNA_GAIN), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_CHAN_MAX_POWER), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_REGION_CODE), +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_CALIBRATED_POWER), + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_CHAN_TARGET_POWER), +#endif OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_STATE), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_MASK), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_PERIOD), @@ -502,7 +506,6 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_RADIO_COEX_ENABLE), #endif - #if OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_ALLOWLIST), @@ -655,6 +658,9 @@ NcpBase::PropertyHandler NcpBase::FindInsertPropertyHandler(spinel_prop_key_t aK } constexpr static HandlerEntry sHandlerEntries[] = { +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + OT_NCP_INSERT_HANDLER_ENTRY(SPINEL_PROP_PHY_CALIBRATED_POWER), +#endif #if OPENTHREAD_MTD || OPENTHREAD_FTD #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE OT_NCP_INSERT_HANDLER_ENTRY(SPINEL_PROP_THREAD_ON_MESH_NETS), diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 185a322b3..e9d13c09e 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -104,6 +104,7 @@ add_library(openthread-posix alarm.cpp backbone.cpp backtrace.cpp + config_file.cpp daemon.cpp entropy.cpp firewall.cpp @@ -115,6 +116,8 @@ add_library(openthread-posix misc.cpp multicast_routing.cpp netif.cpp + power.cpp + power_updater.cpp radio.cpp radio_url.cpp settings.cpp diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 648278764..8c87d234a 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -47,6 +47,7 @@ libopenthread_posix_a_SOURCES = \ alarm.cpp \ backbone.cpp \ backtrace.cpp \ + config_file.cpp \ daemon.cpp \ entropy.cpp \ firewall.cpp \ @@ -58,6 +59,8 @@ libopenthread_posix_a_SOURCES = \ misc.cpp \ multicast_routing.cpp \ netif.cpp \ + power.cpp \ + power_updater.cpp \ radio.cpp \ radio_url.cpp \ settings.cpp \ diff --git a/src/posix/platform/config_file.cpp b/src/posix/platform/config_file.cpp new file mode 100644 index 000000000..dd78c9716 --- /dev/null +++ b/src/posix/platform/config_file.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config_file.hpp" + +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" +#include +#include "common/code_utils.hpp" +#include "lib/platform/exit_code.h" + +namespace ot { +namespace Posix { + +ConfigFile::ConfigFile(const char *aFilePath) + : mFilePath(aFilePath) +{ + assert(mFilePath != nullptr); + VerifyOrDie(strlen(mFilePath) + strlen(kSwapSuffix) < kFileNameMaxSize, OT_EXIT_FAILURE); +} + +otError ConfigFile::Get(const char *aKey, int &aIterator, char *aValue, int aValueLength) +{ + otError error = OT_ERROR_NONE; + char line[kLineMaxSize + 1]; + FILE *fp = nullptr; + char *ret; + long int pos; + + VerifyOrExit((aKey != nullptr) && (aValue != nullptr), error = OT_ERROR_INVALID_ARGS); + VerifyOrExit((fp = fopen(mFilePath, "r")) != nullptr, error = OT_ERROR_NOT_FOUND); + VerifyOrDie(fseek(fp, aIterator, SEEK_SET) == 0, OT_EXIT_ERROR_ERRNO); + + while ((ret = fgets(line, sizeof(line), fp)) != nullptr) + { + char *str; + char *key; + char *value; + + // If the string exceeds the `sizeof(line) - 1`, the string will be truncated to `sizeof(line) - 1` bytes string + // by the function `fgets()`. + if (strlen(line) + 1 == sizeof(line)) + { + // The line is too long. + continue; + } + + // Remove comments + strtok(line, kCommentDelimiter); + + if ((str = strstr(line, "=")) == nullptr) + { + continue; + } + + *str = '\0'; + key = line; + + Strip(key); + + if (strcmp(aKey, key) == 0) + { + value = str + 1; + Strip(value); + aValueLength = OT_MIN(static_cast(strlen(value)), (aValueLength - 1)); + memcpy(aValue, value, static_cast(aValueLength)); + aValue[aValueLength] = '\0'; + break; + } + } + + VerifyOrExit(ret != nullptr, error = OT_ERROR_NOT_FOUND); + VerifyOrDie((pos = ftell(fp)) >= 0, OT_EXIT_ERROR_ERRNO); + aIterator = static_cast(pos); + +exit: + if (fp != nullptr) + { + fclose(fp); + } + + return error; +} + +otError ConfigFile::Add(const char *aKey, const char *aValue) +{ + otError error = OT_ERROR_NONE; + FILE *fp = nullptr; + char *path = nullptr; + char *dir; + struct stat st; + + VerifyOrExit((aKey != nullptr) && (aValue != nullptr), error = OT_ERROR_INVALID_ARGS); + VerifyOrDie((path = strdup(mFilePath)) != nullptr, OT_EXIT_ERROR_ERRNO); + dir = dirname(path); + + if (stat(dir, &st) == -1) + { + VerifyOrDie(mkdir(dir, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == 0, OT_EXIT_ERROR_ERRNO); + } + + VerifyOrDie((fp = fopen(mFilePath, "at")) != NULL, OT_EXIT_ERROR_ERRNO); + VerifyOrDie(fprintf(fp, "%s=%s\n", aKey, aValue) > 0, OT_EXIT_ERROR_ERRNO); + +exit: + if (fp != nullptr) + { + fclose(fp); + } + + if (path != nullptr) + { + free(path); + } + + return error; +} + +otError ConfigFile::Clear(const char *aKey) +{ + otError error = OT_ERROR_NONE; + char swapFile[kFileNameMaxSize]; + char line[kLineMaxSize]; + FILE *fp = nullptr; + FILE *fpSwap = nullptr; + + VerifyOrExit(aKey != nullptr, error = OT_ERROR_INVALID_ARGS); + VerifyOrDie((fp = fopen(mFilePath, "r")) != NULL, OT_EXIT_ERROR_ERRNO); + snprintf(swapFile, sizeof(swapFile), "%s%s", mFilePath, kSwapSuffix); + VerifyOrDie((fpSwap = fopen(swapFile, "w+")) != NULL, OT_EXIT_ERROR_ERRNO); + + while (fgets(line, sizeof(line), fp) != nullptr) + { + bool containsKey; + char *str1; + char *str2; + + str1 = strstr(line, kCommentDelimiter); + str2 = strstr(line, aKey); + + // If only the comment contains the key string, ignore it. + containsKey = (str2 != nullptr) && (str1 == nullptr || str2 < str1); + + if (!containsKey) + { + fputs(line, fpSwap); + } + } + +exit: + if (fp != nullptr) + { + fclose(fp); + } + + if (fpSwap != nullptr) + { + fclose(fpSwap); + } + + if (error == OT_ERROR_NONE) + { + VerifyOrDie(rename(swapFile, mFilePath) == 0, OT_EXIT_ERROR_ERRNO); + } + + return error; +} + +void ConfigFile::Strip(char *aString) +{ + int count = 0; + + for (int i = 0; aString[i]; i++) + { + if (aString[i] != ' ' && aString[i] != '\r' && aString[i] != '\n') + { + aString[count++] = aString[i]; + } + } + + aString[count] = '\0'; +} +} // namespace Posix +} // namespace ot diff --git a/src/posix/platform/config_file.hpp b/src/posix/platform/config_file.hpp new file mode 100644 index 000000000..c0a74ca44 --- /dev/null +++ b/src/posix/platform/config_file.hpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef POSIX_PLATFORM_CONFIG_FILE_HPP_ +#define POSIX_PLATFORM_CONFIG_FILE_HPP_ + +#include +#include +#include + +namespace ot { +namespace Posix { + +/** + * This class provides read/write/clear methods for key/value configuration files. + * + */ +class ConfigFile +{ +public: + /** + * This method initializes the configuration file path. + * + * @param[in] aFilePath A pointer to the null-terminated file path. + * + */ + explicit ConfigFile(const char *aFilePath); + + /** + * This method gets a configuration from the configuration file. + * + * @param[in] aKey The key string associated with the requested configuration. + * @param[in,out] aIterator A reference to an iterator. MUST be initialized to 0 or the behavior is undefined. + * @param[out] aValue A pointer to where the new value string of the configuration should be read from. + * The @p aValue string will be terminated with `\0` if this method returns success. + * @param[in] aValueLength The max length of the data pointed to by @p aValue. + * + * @retval OT_ERROR_NONE The given configuration was found and fetched successfully. + * @retval OT_ERROR_NOT_FOUND The given key or iterator was not found in the configuration file. + * @retval OT_ERROR_INVALID_ARGS If @p aKey or @p aValue was NULL. + * + */ + otError Get(const char *aKey, int &aIterator, char *aValue, int aValueLength); + + /** + * This method adds a configuration to the configuration file. + * + * @param[in] aKey The key string associated with the requested configuration. + * @param[in] aValue A pointer to where the new value string of the configuration should be written. + * + * @retval OT_ERROR_NONE The given key was found and removed successfully. + * @retval OT_ERROR_INVALID_ARGS If @p aKey or @p aValue was NULL. + * + */ + otError Add(const char *aKey, const char *aValue); + + /** + * This function removes all configurations with the same key string from the configuration file. + * + * @param[in] aKey The key string associated with the requested configuration. + * + * @retval OT_ERROR_NONE The given key was found and removed successfully. + * @retval OT_ERROR_INVALID_ARGS If @p aKey was NULL. + * + */ + otError Clear(const char *aKey); + +private: + const char *kCommentDelimiter = "#"; + const char *kSwapSuffix = ".swap"; + static constexpr uint16_t kLineMaxSize = 512; + static constexpr uint16_t kFileNameMaxSize = 255; + + void Strip(char *aString); + + const char *mFilePath; +}; + +} // namespace Posix +} // namespace ot + +#endif // POSIX_PLATFORM_CONFIG_FILE_HPP_ diff --git a/src/posix/platform/openthread-core-posix-config.h b/src/posix/platform/openthread-core-posix-config.h index 325ae52da..efc38c9f8 100644 --- a/src/posix/platform/openthread-core-posix-config.h +++ b/src/posix/platform/openthread-core-posix-config.h @@ -309,4 +309,14 @@ #define OPENTHREAD_CONFIG_ASSERT_CHECK_API_POINTER_PARAM_FOR_NULL 1 #endif +/** + * @def OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + * + * Define as 1 to enable platform power calibration support. + * + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE 1 +#endif + #endif // OPENTHREAD_CORE_POSIX_CONFIG_H_ diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h index 88a463dd7..5ff59ccc7 100644 --- a/src/posix/platform/openthread-posix-config.h +++ b/src/posix/platform/openthread-posix-config.h @@ -335,4 +335,27 @@ #define OPENTHREAD_POSIX_CONFIG_INFRA_IF_ENABLE OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE #endif +/** + * @def OPENTHREAD_POSIX_CONFIG_FACTORY_CONFIG_FILE + * + * Define the path of the factory config file. + * + * Note: The factory config file contains the persist data that configured by the factory. And it won't be changed + * after a device firmware update OTA is done. + * + */ +#ifndef OPENTHREAD_POSIX_CONFIG_FACTORY_CONFIG_FILE +#define OPENTHREAD_POSIX_CONFIG_FACTORY_CONFIG_FILE "src/posix/platform/openthread.conf.example" +#endif + +/** + * @def OPENTHREAD_POSIX_CONFIG_PRODUCT_CONFIG_FILE + * + * Define the path of the product config file. + * + */ +#ifndef OPENTHREAD_POSIX_CONFIG_PRODUCT_CONFIG_FILE +#define OPENTHREAD_POSIX_CONFIG_PRODUCT_CONFIG_FILE "src/posix/platform/openthread.conf.example" +#endif + #endif // OPENTHREAD_PLATFORM_CONFIG_H_ diff --git a/src/posix/platform/openthread.conf.example b/src/posix/platform/openthread.conf.example new file mode 100644 index 000000000..83a2908cc --- /dev/null +++ b/src/posix/platform/openthread.conf.example @@ -0,0 +1,24 @@ +# +# Sample configuration file +# +# Modify this to use your own configurations! +# + +# Target power table entries. +# target_power=,,, +target_power=ETSI,11,26,1000 +target_power=FCC,11,14,1700 +target_power=FCC,15,24,2000 +target_power=FCC,25,26,1600 + +# Region domain mapping table entries. +# region_domain_mapping=,,,... +region_domain_mapping=FCC,AU,CA,CL,CO,IN,MX,PE,TW,US +region_domain_mapping=ETSI,WW + +# Power calibration table entries. +# calibrated_power=,,, +calibrated_power=11,25,1900,112233 +calibrated_power=11,25,1000,223344 +calibrated_power=26,26,1500,334455 +calibrated_power=26,26,700,445566 diff --git a/src/posix/platform/power.cpp b/src/posix/platform/power.cpp new file mode 100644 index 000000000..ea832a38a --- /dev/null +++ b/src/posix/platform/power.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "power.hpp" +#include "common/code_utils.hpp" +#include "utils/parse_cmdline.hpp" + +namespace ot { +namespace Power { + +otError Domain::Set(const char *aDomain) +{ + otError error = OT_ERROR_NONE; + uint16_t length = static_cast(strlen(aDomain)); + + VerifyOrExit(length <= kDomainSize, error = OT_ERROR_INVALID_ARGS); + memcpy(m8, aDomain, length); + m8[length] = '\0'; + +exit: + return error; +} + +otError TargetPower::FromString(char *aString) +{ + otError error = OT_ERROR_NONE; + char *str; + + VerifyOrExit((str = strtok(aString, ",")) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(str, mChannelStart)); + + VerifyOrExit((str = strtok(nullptr, ",")) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(str, mChannelEnd)); + + VerifyOrExit((str = strtok(nullptr, ",")) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsInt16(str, mTargetPower)); + +exit: + return error; +} + +TargetPower::InfoString TargetPower::ToString(void) const +{ + InfoString string; + + string.Append("%u,%u,%d", mChannelStart, mChannelEnd, mTargetPower); + + return string; +} + +otError RawPowerSetting::Set(const char *aRawPowerSetting) +{ + otError error; + uint16_t length = sizeof(mData); + + SuccessOrExit(error = ot::Utils::CmdLineParser::ParseAsHexString(aRawPowerSetting, length, mData)); + mLength = static_cast(length); + +exit: + return error; +} + +RawPowerSetting::InfoString RawPowerSetting::ToString(void) const +{ + InfoString string; + + string.AppendHexBytes(mData, mLength); + + return string; +} + +otError CalibratedPower::FromString(char *aString) +{ + otError error = OT_ERROR_NONE; + char *str; + char *pSave; + + VerifyOrExit((str = strtok_r(aString, ",", &pSave)) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(str, mChannelStart)); + + VerifyOrExit((str = strtok_r(nullptr, ",", &pSave)) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(str, mChannelEnd)); + + VerifyOrExit((str = strtok_r(nullptr, ",", &pSave)) != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsInt16(str, mActualPower)); + SuccessOrExit(error = mRawPowerSetting.Set(pSave)); + +exit: + return error; +} + +CalibratedPower::InfoString CalibratedPower::ToString(void) const +{ + InfoString string; + + string.Append("%u,%u,%d,%s", mChannelStart, mChannelEnd, mActualPower, mRawPowerSetting.ToString().AsCString()); + + return string; +} +} // namespace Power +} // namespace ot diff --git a/src/posix/platform/power.hpp b/src/posix/platform/power.hpp new file mode 100644 index 000000000..dbc92e05e --- /dev/null +++ b/src/posix/platform/power.hpp @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef POSIX_PLATFORM_POWER_H +#define POSIX_PLATFORM_POWER_H + +#include +#include +#include + +#include +#include +#include "common/string.hpp" + +namespace ot { +namespace Power { + +class Domain +{ +public: + Domain(void) { m8[0] = '\0'; } + + /** + * This method sets the regulatory domain from a given null terminated C string. + * + * @param[in] aDomain A regulatory domain name C string. + * + * @retval OT_ERROR_NONE Successfully set the regulatory domain. + * @retval OT_ERROR_INVALID_ARGS Given regulatory domain is too long. + * + */ + otError Set(const char *aDomain); + + /** + * This method overloads operator `==` to evaluate whether or not two `Domain` instances are equal. + * + * @param[in] aOther The other `Domain` instance to compare with. + * + * @retval TRUE If the two `Domain` instances are equal. + * @retval FALSE If the two `Domain` instances not equal. + * + */ + bool operator==(const Domain &aOther) const { return strcmp(m8, aOther.m8) == 0; } + + /** + * This method overloads operator `!=` to evaluate whether or not the `Domain` is unequal to a given C string. + * + * @param[in] aCString A C string to compare with. Can be `nullptr` which then returns 'TRUE'. + * + * @retval TRUE If the two regulatory domains are not equal. + * @retval FALSE If the two regulatory domains are equal. + * + */ + bool operator!=(const char *aCString) const { return (aCString == nullptr) ? true : strcmp(m8, aCString) != 0; } + + /** + * This method gets the regulatory domain as a null terminated C string. + * + * @returns The regulatory domain as a null terminated C string array. + * + */ + const char *AsCString(void) const { return m8; } + +private: + static constexpr uint8_t kDomainSize = 8; + char m8[kDomainSize + 1]; +}; + +class TargetPower +{ +public: + static constexpr uint16_t kInfoStringSize = 12; ///< Recommended buffer size to use with `ToString()`. + typedef String InfoString; + + /** + * This method parses an target power string. + * + * The string MUST follow the format: ",,". + * For example, "11,26,2000" + * + * @param[in] aString A pointer to the null-terminated string. + * + * @retval OT_ERROR_NONE Successfully parsed the target power string. + * @retval OT_ERROR_PARSE Failed to parse the target power string. + * + */ + otError FromString(char *aString); + + /** + * This method returns the start channel. + * + * @returns The channel. + * + */ + uint8_t GetChannelStart(void) const { return mChannelStart; } + + /** + * This method returns the end channel. + * + * @returns The channel. + * + */ + uint8_t GetChannelEnd(void) const { return mChannelEnd; } + + /** + * This method returns the target power. + * + * @returns The target power, in 0.01 dBm. + * + */ + int16_t GetTargetPower(void) const { return mTargetPower; } + + /** + * This method converts the target power into a human-readable string. + * + * @returns An `InfoString` object representing the target power. + * + */ + InfoString ToString(void) const; + +private: + uint8_t mChannelStart; + uint8_t mChannelEnd; + int16_t mTargetPower; +}; + +class RawPowerSetting +{ +public: + // Recommended buffer size to use with `ToString()`. + static constexpr uint16_t kInfoStringSize = OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE * 2 + 1; + typedef String InfoString; + + /** + * This method sets the raw power setting from a given null terminated hex C string. + * + * @param[in] aRawPowerSetting A raw power setting hex C string. + * + * @retval OT_ERROR_NONE Successfully set the raw power setting. + * @retval OT_ERROR_INVALID_ARGS The given raw power setting is too long. + * + */ + otError Set(const char *aRawPowerSetting); + + /** + * This method converts the raw power setting into a human-readable string. + * + * @returns An `InfoString` object representing the calibrated power. + * + */ + InfoString ToString(void) const; + + const uint8_t *GetData(void) const { return mData; } + uint16_t GetLength(void) const { return mLength; } + +private: + static constexpr uint16_t kMaxRawPowerSettingSize = OPENTHREAD_CONFIG_POWER_CALIBRATION_RAW_POWER_SETTING_SIZE; + + uint8_t mData[kMaxRawPowerSettingSize]; + uint16_t mLength; +}; + +class CalibratedPower +{ +public: + // Recommended buffer size to use with `ToString()`. + static constexpr uint16_t kInfoStringSize = 20 + RawPowerSetting::kInfoStringSize; + typedef String InfoString; + + /** + * This method parses an calibrated power string. + * + * The string MUST follow the format: ",,,". + * For example, "11,26,2000,1122aabb" + * + * @param[in] aString A pointer to the null-terminated string. + * + * @retval OT_ERROR_NONE Successfully parsed the calibrated power string. + * @retval OT_ERROR_PARSE Failed to parse the calibrated power string. + * + */ + otError FromString(char *aString); + + /** + * This method returns the start channel. + * + * @returns The channel. + * + */ + uint8_t GetChannelStart(void) const { return mChannelStart; } + + /** + * This method sets the start channel. + * + * @param[in] aChannelStart The start channel. + * + */ + void SetChannelStart(uint8_t aChannelStart) { mChannelStart = aChannelStart; } + + /** + * This method returns the end channel. + * + * @returns The channel. + * + */ + uint8_t GetChannelEnd(void) const { return mChannelEnd; } + + /** + * This method sets the end channel. + * + * @param[in] aChannelEnd The end channel. + * + */ + void SetChannelEnd(uint8_t aChannelEnd) { mChannelEnd = aChannelEnd; } + + /** + * This method returns the actual power. + * + * @returns The actual measured power, in 0.01 dBm. + * + */ + int16_t GetActualPower(void) const { return mActualPower; } + + /** + * This method sets the actual channel. + * + * @param[in] aActualPower The actual power in 0.01 dBm. + * + */ + void SetActualPower(int16_t aActualPower) { mActualPower = aActualPower; } + + /** + * This method returns the raw power setting. + * + * @returns A reference to the raw power setting. + * + */ + const RawPowerSetting &GetRawPowerSetting(void) const { return mRawPowerSetting; } + + /** + * This method sets the raw power setting. + * + * @param[in] aRawPowerSetting The raw power setting. + * + */ + void SetRawPowerSetting(const RawPowerSetting &aRawPowerSetting) { mRawPowerSetting = aRawPowerSetting; } + + /** + * This method converts the calibrated power into a human-readable string. + * + * @returns An `InfoString` object representing the calibrated power. + * + */ + InfoString ToString(void) const; + +private: + uint8_t mChannelStart; + uint8_t mChannelEnd; + int16_t mActualPower; + RawPowerSetting mRawPowerSetting; +}; +} // namespace Power +} // namespace ot +#endif // POSIX_PLATFORM_POWER_H diff --git a/src/posix/platform/power_updater.cpp b/src/posix/platform/power_updater.cpp new file mode 100644 index 000000000..d3b45bb92 --- /dev/null +++ b/src/posix/platform/power_updater.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "power_updater.hpp" + +#include "platform-posix.h" +#include +#include "lib/platform/exit_code.h" + +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +namespace ot { +namespace Posix { + +otError PowerUpdater::SetRegion(uint16_t aRegionCode) +{ + otError error = OT_ERROR_NONE; + int iterator = 0; + Power::Domain domain; + Power::TargetPower targetPower; + + if (GetDomain(aRegionCode, domain) != OT_ERROR_NONE) + { + // If failed to find the domain for the region, use the world wide region as the default region. + VerifyOrExit(GetDomain(kRegionCodeWorldWide, domain) == OT_ERROR_NONE, error = OT_ERROR_FAILED); + } + + while (GetNextTargetPower(domain, iterator, targetPower) == OT_ERROR_NONE) + { + otLogInfoPlat("Update target power: %s\r\n", targetPower.ToString().AsCString()); + + for (uint8_t ch = targetPower.GetChannelStart(); ch <= targetPower.GetChannelEnd(); ch++) + { + SuccessOrExit(error = otPlatRadioSetChannelTargetPower(gInstance, ch, targetPower.GetTargetPower())); + } + } + + SuccessOrExit(error = UpdateCalibratedPower()); + + mRegionCode = aRegionCode; + +exit: + if (error == OT_ERROR_NONE) + { + otLogInfoPlat("Set region \"%c%c\" successfully", (aRegionCode >> 8) & 0xff, (aRegionCode & 0xff)); + } + else + { + otLogCritPlat("Set region \"%c%c\" failed, Error: %s", (aRegionCode >> 8) & 0xff, (aRegionCode & 0xff), + otThreadErrorToString(error)); + } + + return error; +} + +otError PowerUpdater::UpdateCalibratedPower(void) +{ + otError error = OT_ERROR_NONE; + int iterator = 0; + char value[kMaxValueSize]; + Power::CalibratedPower calibratedPower; + ConfigFile *calibrationFile = &mFactoryConfigFile; + + SuccessOrExit(error = otPlatRadioClearCalibratedPowers(gInstance)); + + // If the distribution of output power is large, the factory needs to measure the power calibration data + // for each device individually, and the power calibration data will be written to the factory config file. + // Otherwise, the power calibration data can be pre-configured in the product config file. + if (calibrationFile->Get(kKeyCalibratedPower, iterator, value, sizeof(value)) != OT_ERROR_NONE) + { + calibrationFile = &mProductConfigFile; + } + + iterator = 0; + while (calibrationFile->Get(kKeyCalibratedPower, iterator, value, sizeof(value)) == OT_ERROR_NONE) + { + SuccessOrExit(error = calibratedPower.FromString(value)); + otLogInfoPlat("Update calibrated power: %s\r\n", calibratedPower.ToString().AsCString()); + + for (uint8_t ch = calibratedPower.GetChannelStart(); ch <= calibratedPower.GetChannelEnd(); ch++) + { + SuccessOrExit(error = otPlatRadioAddCalibratedPower(gInstance, ch, calibratedPower.GetActualPower(), + calibratedPower.GetRawPowerSetting().GetData(), + calibratedPower.GetRawPowerSetting().GetLength())); + } + } + +exit: + if (error != OT_ERROR_NONE) + { + otLogCritPlat("Update calibrated power table failed, Error: %s", otThreadErrorToString(error)); + } + + return error; +} + +otError PowerUpdater::GetDomain(uint16_t aRegionCode, Power::Domain &aDomain) +{ + otError error = OT_ERROR_NOT_FOUND; + int iterator = 0; + char value[kMaxValueSize]; + char *str; + + while (mProductConfigFile.Get(kKeyRegionDomainMapping, iterator, value, sizeof(value)) == OT_ERROR_NONE) + { + if ((str = strtok(value, kCommaDelimiter)) == nullptr) + { + continue; + } + + while ((str = strtok(nullptr, kCommaDelimiter)) != nullptr) + { + if ((strlen(str) == 2) && (StringToRegionCode(str) == aRegionCode)) + { + ExitNow(error = aDomain.Set(value)); + } + } + } + +exit: + if (error != OT_ERROR_NONE) + { + otLogCritPlat("Get domain failed, Error: %s", otThreadErrorToString(error)); + } + + return error; +} + +otError PowerUpdater::GetNextTargetPower(const Power::Domain &aDomain, int &aIterator, Power::TargetPower &aTargetPower) +{ + otError error = OT_ERROR_NOT_FOUND; + char value[kMaxValueSize]; + char *domain; + char *psave; + + while (mProductConfigFile.Get(kKeyTargetPower, aIterator, value, sizeof(value)) == OT_ERROR_NONE) + { + if (((domain = strtok_r(value, kCommaDelimiter, &psave)) == nullptr) || (aDomain != domain)) + { + continue; + } + + if ((error = aTargetPower.FromString(psave)) != OT_ERROR_NONE) + { + otLogCritPlat("Read target power failed, Error: %s", otThreadErrorToString(error)); + } + break; + } + + return error; +} + +} // namespace Posix +} // namespace ot +#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE diff --git a/src/posix/platform/power_updater.hpp b/src/posix/platform/power_updater.hpp new file mode 100644 index 000000000..5a8e20aa9 --- /dev/null +++ b/src/posix/platform/power_updater.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef POSIX_PLATFORM_POWER_UPDATER_HPP_ +#define POSIX_PLATFORM_POWER_UPDATER_HPP_ + +#include "openthread-posix-config.h" + +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +#include +#include + +#include +#include +#include + +#include "config_file.hpp" +#include "power.hpp" +#include "common/code_utils.hpp" + +namespace ot { +namespace Posix { + +/** + * This class updates the target power table and calibrated powe table to the RCP. + * + */ +class PowerUpdater +{ +public: + PowerUpdater(void) + : mFactoryConfigFile(kFactoryConfigFile) + , mProductConfigFile(kProductConfigFile) + , mRegionCode(0) + { + } + + /** + * Set the region code. + * + * The radio region format is the 2-bytes ascii representation of the + * ISO 3166 alpha-2 code. + * + * @param[in] aRegionCode The radio region. + * + * @retval OT_ERROR_NONE Successfully set region code. + * @retval OT_ERROR_FAILED Failed to set the region code. + * + */ + otError SetRegion(uint16_t aRegionCode); + + /** + * Get the region code. + * + * The radio region format is the 2-bytes ascii representation of the + * ISO 3166 alpha-2 code. + * + * @returns The region code. + * + */ + uint16_t GetRegion(void) const { return mRegionCode; } + +private: + const char *kFactoryConfigFile = OPENTHREAD_POSIX_CONFIG_FACTORY_CONFIG_FILE; + const char *kProductConfigFile = OPENTHREAD_POSIX_CONFIG_PRODUCT_CONFIG_FILE; + const char *kKeyCalibratedPower = "calibrated_power"; + const char *kKeyTargetPower = "target_power"; + const char *kKeyRegionDomainMapping = "region_domain_mapping"; + const char *kCommaDelimiter = ","; + static constexpr uint16_t kMaxValueSize = 512; + static constexpr uint16_t kRegionCodeWorldWide = 0x5757; // Region Code: "WW" + + uint16_t StringToRegionCode(const char *aString) const + { + return static_cast(((aString[0] & 0xFF) << 8) | ((aString[1] & 0xFF) << 0)); + } + otError GetDomain(uint16_t aRegionCode, Power::Domain &aDomain); + otError GetNextTargetPower(const Power::Domain &aDomain, int &aIterator, Power::TargetPower &aTargetPower); + otError UpdateCalibratedPower(void); + + ConfigFile mFactoryConfigFile; + ConfigFile mProductConfigFile; + uint16_t mRegionCode; +}; + +} // namespace Posix +} // namespace ot + +#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#endif // POSIX_PLATFORM_POWER_UPDATER_HPP_ diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index 3620879ba..bdcc586c3 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -63,6 +63,11 @@ static ot::Spinel::RadioSpinel "OT_POSIX_RCP_BUS_VENDOR!" #endif +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +#include "power_updater.hpp" +static ot::Posix::PowerUpdater sPowerUpdater; +#endif + namespace ot { namespace Posix { @@ -135,7 +140,7 @@ void Radio::Init(void) VerifyOrDie(strnlen(region, 3) == 2, OT_EXIT_INVALID_ARGUMENTS); regionCode = static_cast(static_cast(region[0]) << 8) + static_cast(region[1]); - SuccessOrDie(sRadioSpinel.SetRadioRegion(regionCode)); + SuccessOrDie(otPlatRadioSetRegion(gInstance, regionCode)); } #if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE @@ -701,16 +706,49 @@ otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aCh return sRadioSpinel.SetChannelMaxTransmitPower(aChannel, aMaxPower); } +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +otError otPlatRadioAddCalibratedPower(otInstance *aInstance, + uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + OT_UNUSED_VARIABLE(aInstance); + return sRadioSpinel.AddCalibratedPower(aChannel, aActualPower, aRawPowerSetting, aRawPowerSettingLength); +} + +otError otPlatRadioClearCalibratedPowers(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return sRadioSpinel.ClearCalibratedPowers(); +} + +otError otPlatRadioSetChannelTargetPower(otInstance *aInstance, uint8_t aChannel, int16_t aTargetPower) +{ + OT_UNUSED_VARIABLE(aInstance); + return sRadioSpinel.SetChannelTargetPower(aChannel, aTargetPower); +} +#endif + otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode) { OT_UNUSED_VARIABLE(aInstance); +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + return sPowerUpdater.SetRegion(aRegionCode); +#else return sRadioSpinel.SetRadioRegion(aRegionCode); +#endif } otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode) { OT_UNUSED_VARIABLE(aInstance); +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + *aRegionCode = sPowerUpdater.GetRegion(); + return OT_ERROR_NONE; +#else return sRadioSpinel.GetRadioRegion(aRegionCode); +#endif } #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE diff --git a/tests/scripts/expect/ot-fct.exp b/tests/scripts/expect/ot-fct.exp new file mode 100755 index 000000000..5fff5d9d5 --- /dev/null +++ b/tests/scripts/expect/ot-fct.exp @@ -0,0 +1,48 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2022, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +source "tests/scripts/expect/_common.exp" + +spawn build/posix/tools/ot-fct/ot-fct + +send "targetpowertable\n" +expect_line "Done" +send "powercalibrationtable\n" +expect_line "Done" +send "powercalibrationtable add -b 11,25 -c 1900,112233/1000,223344 -b 26,26 -c 1500,334455/700,445566\n" +expect_line "Done" +send "powercalibrationtable clear\n" +expect_line "Done" +send "regiondomaintable\n" +expect "Done" +send "invalidcommand\n" +expect "failed" +expect "status 0x17" +send "\x04" +expect eof diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 34d9a187f..e2246c1e5 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -812,6 +812,27 @@ target_link_libraries(ot-test-pool add_test(NAME ot-test-pool COMMAND ot-test-pool) +add_executable(ot-test-power-calibration + test_power_calibration.cpp +) + +target_include_directories(ot-test-power-calibration + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-power-calibration + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-power-calibration + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-power-calibration COMMAND ot-test-power-calibration) + add_executable(ot-test-priority-queue test_priority_queue.cpp ) diff --git a/tests/unit/test_power_calibration.cpp b/tests/unit/test_power_calibration.cpp new file mode 100644 index 000000000..91c1d6920 --- /dev/null +++ b/tests/unit/test_power_calibration.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "test_platform.h" +#include "test_util.h" + +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +namespace ot { + +void TestPowerCalibration(void) +{ + otInstance *instance; + uint8_t rawPowerSetting[2]; + uint16_t rawPowerSettingLength; + + struct CalibratedPowerEntry + { + uint8_t mChannel; + int16_t mActualPower; + uint8_t mRawPowerSetting[1]; + uint16_t mRawPowerSettingLength; + }; + + constexpr CalibratedPowerEntry kCalibratedPowerTable[] = { + {11, 15000, {0x02}, 1}, + {11, 5000, {0x00}, 1}, + {11, 10000, {0x01}, 1}, + }; + + instance = static_cast(testInitInstance()); + VerifyOrQuit(instance != nullptr, "Null OpenThread instance"); + + for (const CalibratedPowerEntry &calibratedPower : kCalibratedPowerTable) + { + SuccessOrQuit(otPlatRadioAddCalibratedPower(instance, calibratedPower.mChannel, calibratedPower.mActualPower, + calibratedPower.mRawPowerSetting, + calibratedPower.mRawPowerSettingLength)); + } + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 4999)); + rawPowerSettingLength = sizeof(rawPowerSetting); + VerifyOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength) == + OT_ERROR_NOT_FOUND); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 5000)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x00); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 9999)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x00); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 10000)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x01); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 14999)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x01); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 15000)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x02); + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 15001)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x02); + + rawPowerSettingLength = sizeof(rawPowerSetting); + VerifyOrQuit(otPlatRadioGetRawPowerSetting(instance, 12, rawPowerSetting, &rawPowerSettingLength) == + OT_ERROR_NOT_FOUND); + + SuccessOrQuit(otPlatRadioClearCalibratedPowers(instance)); + rawPowerSettingLength = sizeof(rawPowerSetting); + VerifyOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength) == + OT_ERROR_NOT_FOUND); + + for (const CalibratedPowerEntry &calibratedPower : kCalibratedPowerTable) + { + SuccessOrQuit(otPlatRadioAddCalibratedPower(instance, calibratedPower.mChannel, calibratedPower.mActualPower, + calibratedPower.mRawPowerSetting, + calibratedPower.mRawPowerSettingLength)); + } + + SuccessOrQuit(otPlatRadioSetChannelTargetPower(instance, 11, 15000)); + rawPowerSettingLength = sizeof(rawPowerSetting); + SuccessOrQuit(otPlatRadioGetRawPowerSetting(instance, 11, rawPowerSetting, &rawPowerSettingLength)); + VerifyOrQuit(rawPowerSettingLength == 1); + VerifyOrQuit(rawPowerSetting[0] == 0x02); + + VerifyOrQuit( + otPlatRadioAddCalibratedPower(instance, kCalibratedPowerTable[0].mChannel, + kCalibratedPowerTable[0].mActualPower, kCalibratedPowerTable[0].mRawPowerSetting, + kCalibratedPowerTable[0].mRawPowerSettingLength) == OT_ERROR_INVALID_ARGS); + + testFreeInstance(instance); +} +} // namespace ot + +#endif // OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +int main(void) +{ +#if OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + ot::TestPowerCalibration(); + printf("All tests passed\n"); +#else // OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + printf("Power calibration is not enabled\n"); +#endif // OPENTHREAD_CONFIG_POWER_CALIBRATION_ENABLE && OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + return 0; +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..5822c05f6 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2022, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +if(OT_PLATFORM STREQUAL "posix") + add_subdirectory(ot-fct) +endif() diff --git a/tools/ot-fct/CMakeLists.txt b/tools/ot-fct/CMakeLists.txt new file mode 100644 index 000000000..aea348f0f --- /dev/null +++ b/tools/ot-fct/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# Copyright (c) 2022, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +project(ot-fct) + +set(OPENTHREAD_DIR ${PROJECT_SOURCE_DIR}/../../) + +include_directories( + ${OPENTHREAD_DIR}/include + ${OPENTHREAD_DIR}/src + ${OPENTHREAD_DIR}/src/core + ${OPENTHREAD_DIR}/src/posix/platform +) + +add_executable(ot-fct + cli.cpp + main.cpp + logging.cpp + ${OPENTHREAD_DIR}/src/core/common/string.cpp + ${OPENTHREAD_DIR}/src/core/utils/parse_cmdline.cpp + ${OPENTHREAD_DIR}/src/lib/platform/exit_code.c + ${OPENTHREAD_DIR}/src/posix/platform/power.cpp + ${OPENTHREAD_DIR}/src/posix/platform/config_file.cpp +) diff --git a/tools/ot-fct/README.md b/tools/ot-fct/README.md new file mode 100644 index 000000000..d006dc675 --- /dev/null +++ b/tools/ot-fct/README.md @@ -0,0 +1,75 @@ +# OpenThread Factory Tool Reference + +## Overview + +The ot-fct is used to store the power calibration table into the factory configuration file and show the power related tables. + +## Command List + +- [powercalibrationtable](#powercalibrationtable) +- [regiondomaintable](#regiondomaintable) +- [targetpowertable](#targetpowertable) + +#### powercalibrationtable + +Show the power calibration table. + +```bash +> powercalibrationtable +| ChStart | ChEnd | ActualPower(0.01dBm) | RawPowerSetting | ++---------+---------+----------------------+-----------------+ +| 11 | 25 | 1900 | 112233 | +| 11 | 25 | 1000 | 223344 | +| 26 | 26 | 1500 | 334455 | +| 26 | 26 | 700 | 445566 | +Done +``` + +#### powercalibrationtable add -b \,\ -c \,\/... ... + +Add power calibration table entry. + +- channelstart: Sub-band start channel. +- channelend: Sub-band end channel. +- actualpower: The actual power in 0.01 dBm. +- rawpowersetting: The raw power setting hex string. + +```bash +> powercalibrationtable add -b 11,25 -c 1900,112233/1000,223344 -b 26,26 -c 1500,334455/700,445566 +Done +``` + +#### powercalibrationtable clear + +Clear the power calibration table. + +```bash +> powercalibrationtable clear +Done +``` + +#### regiondomaintable + +Show the region and regulatory domain mapping table. + +```bash +> regiondomaintable +FCC,AU,CA,CL,CO,IN,MX,PE,TW,US +ETSI,WW +Done +``` + +#### targetpowertable + +Show the target power table. + +```bash +> targetpowertable +| Domain | ChStart | ChEnd | TargetPower(0.01dBm) | ++----------+---------+---------+----------------------+ +| FCC | 11 | 14 | 1700 | +| FCC | 15 | 24 | 2000 | +| FCC | 25 | 26 | 1600 | +| ETSI | 11 | 26 | 1000 | +Done +``` diff --git a/tools/ot-fct/cli.cpp b/tools/ot-fct/cli.cpp new file mode 100644 index 000000000..893e595b4 --- /dev/null +++ b/tools/ot-fct/cli.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "cli.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "power.hpp" +#include "common/code_utils.hpp" + +namespace ot { +namespace Fct { + +const struct Cli::Command Cli::sCommands[] = { + {"powercalibrationtable", &Cli::ProcessCalibrationTable}, + {"targetpowertable", &Cli::ProcessTargetPowerTable}, + {"regiondomaintable", &Cli::ProcessRegionDomainTable}, +}; + +otError Cli::GetNextTargetPower(const Power::Domain &aDomain, int &aIterator, Power::TargetPower &aTargetPower) +{ + otError error = OT_ERROR_NOT_FOUND; + char value[kMaxValueSize]; + char *domain; + char *psave; + + while (mProductConfigFile.Get(kKeyTargetPower, aIterator, value, sizeof(value)) == OT_ERROR_NONE) + { + if (((domain = strtok_r(value, kCommaDelimiter, &psave)) == nullptr) || (aDomain != domain)) + { + continue; + } + + error = aTargetPower.FromString(psave); + break; + } + + return error; +} + +otError Cli::GetNextDomain(int &aIterator, Power::Domain &aDomain) +{ + otError error = OT_ERROR_NOT_FOUND; + char value[kMaxValueSize]; + char *str; + + while (mProductConfigFile.Get(kKeyRegionDomainMapping, aIterator, value, sizeof(value)) == OT_ERROR_NONE) + { + if ((str = strtok(value, kCommaDelimiter)) == nullptr) + { + continue; + } + + error = aDomain.Set(str); + break; + } + +exit: + return error; +} + +otError Cli::ProcessTargetPowerTable(Utils::CmdLineParser::Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + int iterator = 0; + Power::Domain domain; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + printf("| Domain | ChStart | ChEnd | TargetPower(0.01dBm) |\r\n"); + printf("+----------+---------+---------+----------------------+\r\n"); + while (GetNextDomain(iterator, domain) == OT_ERROR_NONE) + { + int iter = 0; + Power::TargetPower targetPower; + + while (GetNextTargetPower(domain, iter, targetPower) == OT_ERROR_NONE) + { + printf("| %-8s | %-7d | %-7d | %-20d |\r\n", domain.AsCString(), targetPower.GetChannelStart(), + targetPower.GetChannelEnd(), targetPower.GetTargetPower()); + } + } + +exit: + return error; +} + +otError Cli::ProcessRegionDomainTable(Utils::CmdLineParser::Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + int iterator = 0; + char value[kMaxValueSize]; + char *domain; + char *psave; + + VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + while (mProductConfigFile.Get(kKeyRegionDomainMapping, iterator, value, sizeof(value)) == OT_ERROR_NONE) + { + printf("%s\r\n", value); + } + +exit: + return error; +} + +otError Cli::ParseNextCalibratedPower(char *aCalibratedPowerString, + uint16_t aLength, + uint16_t &aIterator, + Power::CalibratedPower &aCalibratedPower) +{ + otError error = OT_ERROR_NONE; + char *start = aCalibratedPowerString + aIterator; + char *end; + char *subString; + int16_t actualPower; + ot::Power::RawPowerSetting rawPowerSetting; + + VerifyOrExit(aIterator < aLength, error = OT_ERROR_PARSE); + + end = strstr(start, "/"); + if (end != nullptr) + { + aIterator = end - aCalibratedPowerString + 1; // +1 to skip '/' + *end = '\0'; + } + else + { + aIterator = aLength; + end = aCalibratedPowerString + aLength; + } + + subString = strstr(start, kCommaDelimiter); + VerifyOrExit(subString != nullptr, error = OT_ERROR_PARSE); + *subString = '\0'; + subString++; + + SuccessOrExit(error = Utils::CmdLineParser::ParseAsInt16(start, actualPower)); + aCalibratedPower.SetActualPower(actualPower); + + VerifyOrExit(subString < end, error = OT_ERROR_PARSE); + SuccessOrExit(error = rawPowerSetting.Set(subString)); + aCalibratedPower.SetRawPowerSetting(rawPowerSetting); + +exit: + return error; +} + +otError Cli::ProcessCalibrationTable(Utils::CmdLineParser::Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgs[0].IsEmpty()) + { + int iterator = 0; + char value[kMaxValueSize]; + + ot::Power::CalibratedPower calibratedPower; + + printf("| ChStart | ChEnd | ActualPower(0.01dBm) | RawPowerSetting |\r\n"); + printf("+---------+---------+----------------------+-----------------+\r\n"); + + while (mFactoryConfigFile.Get(kKeyCalibratedPower, iterator, value, sizeof(value)) == OT_ERROR_NONE) + { + SuccessOrExit(error = calibratedPower.FromString(value)); + printf("| %-7d | %-7d | %-20d | %-15s |\r\n", calibratedPower.GetChannelStart(), + calibratedPower.GetChannelEnd(), calibratedPower.GetActualPower(), + calibratedPower.GetRawPowerSetting().ToString().AsCString()); + } + } + else if (aArgs[0] == "add") + { + constexpr uint16_t kStateSearchDomain = 0; + constexpr uint16_t kStateSearchPower = 1; + + uint8_t state = kStateSearchDomain; + char *subString; + uint8_t channel; + Power::CalibratedPower calibratedPower; + + for (Utils::CmdLineParser::Arg *arg = &aArgs[1]; !arg->IsEmpty(); arg++) + { + if ((state == kStateSearchDomain) && (*arg == "-b")) + { + arg++; + VerifyOrExit(!arg->IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + subString = strtok(arg->GetCString(), kCommaDelimiter); + VerifyOrExit(subString != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(subString, channel)); + calibratedPower.SetChannelStart(channel); + + subString = strtok(NULL, kCommaDelimiter); + VerifyOrExit(subString != nullptr, error = OT_ERROR_PARSE); + SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint8(subString, channel)); + calibratedPower.SetChannelEnd(channel); + VerifyOrExit(calibratedPower.GetChannelStart() <= calibratedPower.GetChannelEnd(), + error = OT_ERROR_INVALID_ARGS); + + state = kStateSearchPower; + } + else if ((state == kStateSearchPower) && (*arg == "-c")) + { + uint16_t length; + uint16_t iterator = 0; + + arg++; + VerifyOrExit(!arg->IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + length = strlen(arg->GetCString()); + while (ParseNextCalibratedPower(arg->GetCString(), length, iterator, calibratedPower) == OT_ERROR_NONE) + { + SuccessOrExit( + error = mFactoryConfigFile.Add(kKeyCalibratedPower, calibratedPower.ToString().AsCString())); + } + + state = kStateSearchDomain; + } + else + { + error = OT_ERROR_INVALID_ARGS; + break; + } + } + + if (state == kStateSearchPower) + { + error = OT_ERROR_INVALID_ARGS; + } + } + else if (aArgs[0] == "clear") + { + error = mFactoryConfigFile.Clear(kKeyCalibratedPower); + } + else + { + error = OT_ERROR_INVALID_ARGS; + } + +exit: + return error; +} + +void Cli::ProcessCommand(Utils::CmdLineParser::Arg aArgs[]) +{ + otError error = OT_ERROR_NOT_FOUND; + int i; + + for (i = 0; i < (sizeof(sCommands) / sizeof(sCommands[0])); i++) + { + if (strcmp(aArgs[0].GetCString(), sCommands[i].mName) == 0) + { + error = (this->*sCommands[i].mCommand)(aArgs + 1); + break; + } + } + +exit: + AppendErrorResult(error); +} + +void Cli::ProcessLine(char *aLine) +{ + const int kMaxArgs = 20; + Utils::CmdLineParser::Arg args[kMaxArgs + 1]; + + SuccessOrExit(ot::Utils::CmdLineParser::ParseCmd(aLine, args, kMaxArgs)); + VerifyOrExit(!args[0].IsEmpty()); + + ProcessCommand(args); + +exit: + OutputPrompt(); +} + +void Cli::OutputPrompt(void) +{ + printf("> "); + fflush(stdout); +} + +void Cli::AppendErrorResult(otError aError) +{ + if (aError != OT_ERROR_NONE) + { + printf("failed\r\nstatus %#x\r\n", aError); + } + else + { + printf("Done\r\n"); + } + + fflush(stdout); +} +} // namespace Fct +} // namespace ot diff --git a/tools/ot-fct/cli.hpp b/tools/ot-fct/cli.hpp new file mode 100644 index 000000000..e679ef593 --- /dev/null +++ b/tools/ot-fct/cli.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLI_H +#define CLI_H + +#include "openthread-posix-config.h" + +#include +#include + +#include "config_file.hpp" +#include "power.hpp" +#include "utils/parse_cmdline.hpp" + +#include +#include + +namespace ot { +namespace Fct { + +class Cli; + +/** + * This class implements the factory CLI. + * + */ +class Cli +{ +public: + Cli(void) + : mFactoryConfigFile(OPENTHREAD_POSIX_CONFIG_FACTORY_CONFIG_FILE) + , mProductConfigFile(OPENTHREAD_POSIX_CONFIG_PRODUCT_CONFIG_FILE) + { + } + + /** + * This method processes a factory command. + * + * @param[in] aArgs The arguments of command line. + * @param[in] aArgsLength The number of args in @p aArgs. + * + */ + void ProcessCommand(Utils::CmdLineParser::Arg aArgs[]); + + /** + * This method processes the command line. + * + * @param[in] aLine A pointer to a command line string. + * + */ + void ProcessLine(char *aLine); + + /** + * This method outputs the prompt. + * + */ + void OutputPrompt(void); + +private: + static constexpr uint16_t kMaxValueSize = 512; + const char *kKeyCalibratedPower = "calibrated_power"; + const char *kKeyTargetPower = "target_power"; + const char *kKeyRegionDomainMapping = "region_domain_mapping"; + const char *kCommaDelimiter = ","; + + struct Command + { + const char *mName; + otError (Cli::*mCommand)(Utils::CmdLineParser::Arg aArgs[]); + }; + + otError ParseNextCalibratedPower(char *aCalibratedPowerString, + uint16_t aLength, + uint16_t &aIterator, + Power::CalibratedPower &aCalibratedPower); + otError ProcessCalibrationTable(Utils::CmdLineParser::Arg aArgs[]); + otError ProcessTargetPowerTable(Utils::CmdLineParser::Arg aArgs[]); + otError ProcessRegionDomainTable(Utils::CmdLineParser::Arg aArgs[]); + otError GetNextDomain(int &aIterator, Power::Domain &aDomain); + otError GetNextTargetPower(const Power::Domain &aDomain, int &aIterator, Power::TargetPower &aTargetPower); + + void AppendErrorResult(otError aError); + + static const struct Command sCommands[]; + + ot::Posix::ConfigFile mFactoryConfigFile; + ot::Posix::ConfigFile mProductConfigFile; +}; +} // namespace Fct +} // namespace ot +#endif diff --git a/tools/ot-fct/logging.cpp b/tools/ot-fct/logging.cpp new file mode 100644 index 000000000..344b16264 --- /dev/null +++ b/tools/ot-fct/logging.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include + +void otLogCritPlat(const char *aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + vprintf(aFormat, args); + va_end(args); +} diff --git a/tools/ot-fct/main.cpp b/tools/ot-fct/main.cpp new file mode 100644 index 000000000..ffa98f315 --- /dev/null +++ b/tools/ot-fct/main.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must strain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include "cli.hpp" + +static ot::Fct::Cli sCli; + +int main(int argc, char *argv[]) +{ + if (argc >= 2) + { + const int kMaxArgs = 20; + ot::Utils::CmdLineParser::Arg args[kMaxArgs + 1]; + + if (argc - 1 > kMaxArgs) + { + fprintf(stderr, "Too many arguments!\r\n"); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < argc - 1; i++) + { + args[i].SetCString(argv[i + 1]); + } + args[argc - 1].Clear(); + + sCli.ProcessCommand(args); + } + else + { + fd_set rset; + int maxFd; + int ret; + + sCli.OutputPrompt(); + + while (true) + { + FD_ZERO(&rset); + FD_SET(STDIN_FILENO, &rset); + maxFd = STDIN_FILENO + 1; + + ret = select(maxFd, &rset, nullptr, nullptr, nullptr); + + if ((ret == -1) && (errno != EINTR)) + { + fprintf(stderr, "select: %s\n", strerror(errno)); + break; + } + else if (ret > 0) + { + if (FD_ISSET(STDIN_FILENO, &rset)) + { + const int kBufferSize = 512; + char buffer[kBufferSize]; + + if (fgets(buffer, sizeof(buffer), stdin) != nullptr) + { + sCli.ProcessLine(buffer); + } + else + { + break; + } + } + } + } + } + + return EXIT_SUCCESS; +}