diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml index fceebd0c824..aa0bc249840 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 b42e7acece1..063dafe61f9 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 7a31b16d376..84b90d8b67d 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 f17af97a9b0..78bd55f16fb 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 981523bfba3..34f294fe15f 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 a4b916bba0c..67a3619d6e3 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 a6b56eeda1a..c8ecbc71a64 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 71933223855..078ab4ed8c7 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 fb6cd20eb47..77f06e89373 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 bcdfd6ddd1d..e97ea08bd87 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 c5e37dbae38..6789f454e8c 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 @@ class Instance : public otInstance, private NonCopyable #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 ca132dbae33..9fa475bdf62 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 00000000000..56e9467e2c5 --- /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 00d114975b0..1a52d6e3b15 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 00000000000..b23bdcf0d41 --- /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 00000000000..7b1afa25f98 --- /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 bc4d633970e..dc3118c6175 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -886,6 +886,55 @@ template class RadioSpinel */ 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 0952453a4a9..567809afba7 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 dc4eb5eaf1a..72f440d7615 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 2e70e7da872..a096a7f709d 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 16ab57b3a5e..576740d40d8 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -2566,6 +2566,44 @@ otError NcpBase::DecodeLinkMetrics(otLinkMetrics *aMetrics, bool aAllowPduCount) } #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 89b1f61fece..973e0595e3c 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 185a322b32e..e9d13c09ec6 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 64827876451..8c87d234a24 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 00000000000..dd78c9716d9 --- /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 00000000000..c0a74ca448a --- /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 325ae52da8f..efc38c9f8b3 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 88a463dd72f..5ff59ccc742 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 00000000000..83a2908cc70 --- /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 00000000000..ea832a38a88 --- /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 00000000000..dbc92e05e40 --- /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 00000000000..d3b45bb9241 --- /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 00000000000..5a8e20aa91e --- /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 3620879ba45..bdcc586c3be 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 00000000000..5fff5d9d5be --- /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 34d9a187f0e..e2246c1e538 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 00000000000..91c1d692055 --- /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 00000000000..5822c05f6b2 --- /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 00000000000..aea348f0f31 --- /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 00000000000..d006dc67572 --- /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 00000000000..893e595b4a4 --- /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 00000000000..e679ef5931d --- /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 00000000000..344b162644d --- /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 00000000000..ffa98f315d8 --- /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; +}