diff --git a/examples/platforms/posix/misc.c b/examples/platforms/posix/misc.c index 5b995e80a..c70049bee 100644 --- a/examples/platforms/posix/misc.c +++ b/examples/platforms/posix/misc.c @@ -42,8 +42,9 @@ extern int gArgumentsCount; extern char **gArguments; #endif -static otPlatResetReason sPlatResetReason = OT_PLAT_RESET_REASON_POWER_ON; -bool gPlatformPseudoResetWasRequested; +static otPlatResetReason sPlatResetReason = OT_PLAT_RESET_REASON_POWER_ON; +bool gPlatformPseudoResetWasRequested; +static otPlatMcuPowerState gPlatMcuPowerState = OT_PLAT_MCU_POWER_STATE_ON; void otPlatReset(otInstance *aInstance) { @@ -92,12 +93,27 @@ void otPlatWakeHost(void) otError otPlatSetMcuPowerState(otInstance *aInstance, otPlatMcuPowerState aState) { + otError error = OT_ERROR_NONE; + (void)aInstance; - return (aState == OT_PLAT_MCU_POWER_STATE_ON) ? OT_ERROR_NONE : OT_ERROR_FAILED; + + switch (aState) + { + case OT_PLAT_MCU_POWER_STATE_ON: + case OT_PLAT_MCU_POWER_STATE_LOW_POWER: + gPlatMcuPowerState = aState; + break; + + default: + error = OT_ERROR_FAILED; + break; + } + + return error; } otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance) { (void)aInstance; - return OT_PLAT_MCU_POWER_STATE_ON; + return gPlatMcuPowerState; } diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index 6da446eca..d7fe3939a 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -184,5 +184,25 @@ #define OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT 1 +/** + * @def OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + * + * Define to 1 to enable support controlling of desired power state of NCP's micro-controller. + * + * The power state specifies the desired power state of NCP's micro-controller (MCU) when the underlying platform's + * operating system enters idle mode (i.e., all active tasks/events are processed and the MCU can potentially enter a + * energy-saving power state). + * + * The power state primarily determines how the host should interact with the NCP and whether the host needs an + * external trigger (a "poke") before it can communicate with the NCP or not. + * + * When enabled, the platform is expected to provide `otPlatSetMcuPowerState()` and `otPlatGetMcuPowerState()` + * functions (please see `openthread/platform/misc.h`). Host can then control the power state using + * `SPINEL_PROP_MCU_POWER_STATE`. + * + */ +#define OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL 1 + + #endif /* OPENTHREAD_CORE_TORANJ_CONFIG_H_ */ diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 9dc622234..7dd640f3a 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -114,5 +114,6 @@ run test-005-discover-scan.py run test-006-traffic-router-end-device.py run test-007-traffic-router-sleepy.py run test-008-permit-join.py +run test-100-mcu-power-state.py exit 0 diff --git a/tests/toranj/test-100-mcu-power-state.py b/tests/toranj/test-100-mcu-power-state.py new file mode 100644 index 000000000..8fd5a986b --- /dev/null +++ b/tests/toranj/test-100-mcu-power-state.py @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, 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. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: Testing controlling of NCP's MCU power state + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +node = wpan.Node() + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +# Verify that state is ON after a reset +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check power state wpantund property get and set + +node.form("mcu-power-state") +verify(node.is_associated()) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, 'low-power') +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, 'on') +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, 'lp') # special short-form string for low-power +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED) + +# Verify that `wpantund` will restore the user-set value after NCP reset + +node.reset() +time.sleep(1) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Check the `wpantund` state changes between "deep-sleep" and "offline" + +node.leave() +verify(not node.is_associated()) + +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE) + +# Setting the power state to `low-power` should change wpantund state to `DEEP_SLEEP` + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) + +# Verify that reading/getting a property does not impact the wpantund state. + +node.get(wpan.WPAN_THREAD_RLOC16) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) + +# Setting the power state to `on` should change wpantund state to `OFFLINE` + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Verify the behavior of `begin-low-power` wpanctl command + +node.wpanctl('begin-low-power') +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE) + +# Check the `wpantund` state changes between "offline:commissioned" and "deep-sleep" + +node.form("test-network") +node.set('Daemon:AutoAssociateAfterReset','0') + +# Verify that issuing a `begin-low-power` when in "associated" state +# does not change the state. +node.wpanctl('begin-low-power') +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED) + +# After reset, power state should remain `LOW_POWER` (wpantund would restore the value +# on NCP) and since "AutoAssociateAfterReset" is disabled, wpantund state should +# be `DEEP_SLEEP`. + +node.reset() +time.sleep(2) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_COMMISSIONED) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_ON) +node.leave() + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Verify sleep behavior after disabling `wpantund` ("Daemon:Enabled" property) when state is "offline" + +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE) +verify(node.get('Daemon:Enabled') == 'true') + +# Disabling `wpantund` should put the NCP to deep sleep +node.set('Daemon:Enabled', 'false'); +verify(node.get('Daemon:Enabled') == 'false') +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +# Enabling `wpantund` should update the `MCU_POWER_STATE` back to `ON`. +node.set('Daemon:Enabled', 'true'); +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Verify sleep behavior after disabling `wpantund` ("Daemon:Enabled" property) when state is "associated" + +node.form("disable-test") +verify(node.is_associated()) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +node.set('Daemon:Enabled', 'false'); +verify(node.get('Daemon:Enabled') == 'false') +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_DEEP_SLEEP) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +node.set('Daemon:Enabled', 'true'); +verify(node.get(wpan.WPAN_STATE) == wpan.STATE_COMMISSIONED) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_ON) + +node.leave() + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Verify `AutoAssociateAfterReset` behavior after reset from "deep-sleep" (but commissioned). + +node.set('Daemon:AutoAssociateAfterReset', '1') + +node.set(wpan.WPAN_NCP_MCU_POWER_STATE, wpan.MCU_POWER_STATE_LOW_POWER) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +node.form("resume-test") +verify(node.is_associated()) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) + +node.reset() + +# After reset, power state should remain `LOW_POWER` (wpantund would restore the value +# on NCP) and wpantund state should start as "deep-sleep" but since AutoAssociateAfterReset +# is enabled, network should be recovered. + +time.sleep(1) +verify(node.get(wpan.WPAN_NCP_MCU_POWER_STATE) == wpan.MCU_POWER_STATE_LOW_POWER) +wpan_state = node.get(wpan.WPAN_STATE) +verify(wpan_state == wpan.STATE_ASSOCIATED or wpan_state == wpan.STATE_ASSOCIATING) + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 85108f454..7721b7dc2 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -52,6 +52,7 @@ WPAN_NODE_TYPE = 'Network:NodeType' WPAN_ROLE = 'Network:Role' WPAN_PARTITION_ID = 'Network:PartitionId' WPAN_NCP_VERSION = 'NCP:Version' +WPAN_NCP_MCU_POWER_STATE = "NCP:MCUPowerState" WPAN_NETWORK_ALLOW_JOIN = 'com.nestlabs.internal:Network:AllowingJoin' WPAN_NETWORK_PASSTHRU_PORT = 'com.nestlabs.internal:Network:PassthruPort' @@ -140,6 +141,13 @@ STATE_ISOLATED = '"associated:no-parent"' STATE_NETWAKE_ASLEEP = '"associated:netwake-asleep"' STATE_NETWAKE_WAKING = '"associated:netwake-waking"' +#----------------------------------------------------------------------------------------------------------------------- +# MCU Power state from `WPAN_NCP_MCU_POWER_STATE` + +MCU_POWER_STATE_ON = '"on"' +MCU_POWER_STATE_LOW_POWER = '"low-power"' +MCU_POWER_STATE_OFF = '"off"' + #----------------------------------------------------------------------------------------------------------------------- # Node types (from `WPAN_NODE_TYPE` property)