| /* |
| * Copyright (c) 2016, 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 implements the diagnostics module. |
| */ |
| |
| #include <openthread/config.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include "utils/wrap_string.h" |
| |
| #include "diag_process.hpp" |
| #include "common/code_utils.hpp" |
| |
| namespace ot { |
| namespace Diagnostics { |
| |
| const struct Command Diag::sCommands[] = |
| { |
| { "start", &ProcessStart }, |
| { "stop", &ProcessStop }, |
| { "channel", &ProcessChannel }, |
| { "power", &ProcessPower }, |
| { "send", &ProcessSend }, |
| { "repeat", &ProcessRepeat }, |
| { "sleep", &ProcessSleep }, |
| { "stats", &ProcessStats }, |
| }; |
| |
| char Diag::sDiagOutput[MAX_DIAG_OUTPUT]; |
| struct DiagStats Diag::sStats; |
| |
| int8_t Diag::sTxPower; |
| uint8_t Diag::sChannel; |
| uint8_t Diag::sTxLen; |
| uint32_t Diag::sTxPeriod; |
| uint32_t Diag::sTxPackets; |
| otRadioFrame * Diag::sTxPacket; |
| bool Diag::sRepeatActive; |
| |
| otInstance *Diag::sContext; |
| |
| void Diag::Init(otInstance *aInstance) |
| { |
| sContext = aInstance; |
| sChannel = 20; |
| sTxPower = 0; |
| sTxPeriod = 0; |
| sTxLen = 0; |
| sTxPackets = 0; |
| sRepeatActive = false; |
| memset(&sStats, 0, sizeof(struct DiagStats)); |
| sTxPacket = otPlatRadioGetTransmitBuffer(sContext); |
| otPlatDiagChannelSet(sChannel); |
| otPlatDiagTxPowerSet(sTxPower); |
| } |
| |
| char *Diag::ProcessCmd(int argc, char *argv[]) |
| { |
| unsigned int i; |
| |
| if (argc == 0) |
| { |
| snprintf(sDiagOutput, sizeof(sDiagOutput), "diagnostics mode is %s\r\n", otPlatDiagModeGet() ? "enabled" : "disabled"); |
| } |
| else |
| { |
| for (i = 0; i < sizeof(sCommands) / sizeof(sCommands[0]); i++) |
| { |
| if (strcmp(argv[0], sCommands[i].mName) == 0) |
| { |
| sCommands[i].mCommand(argc - 1, argc > 1 ? &argv[1] : NULL, sDiagOutput, sizeof(sDiagOutput)); |
| break; |
| } |
| } |
| |
| // more platform specific features will be processed under platform layer |
| if (i == sizeof(sCommands) / sizeof(sCommands[0])) |
| { |
| otPlatDiagProcess(sContext, argc, argv, sDiagOutput, sizeof(sDiagOutput)); |
| } |
| } |
| |
| return sDiagOutput; |
| } |
| |
| bool Diag::isEnabled() |
| { |
| return otPlatDiagModeGet(); |
| } |
| |
| void Diag::AppendErrorResult(otError aError, char *aOutput, size_t aOutputMaxLen) |
| { |
| if (aError != OT_ERROR_NONE) |
| { |
| snprintf(aOutput, aOutputMaxLen, "failed\r\nstatus %#x\r\n", aError); |
| } |
| } |
| |
| void Diag::ProcessStart(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| // enable radio |
| otPlatRadioEnable(sContext); |
| |
| // enable promiscuous mode |
| otPlatRadioSetPromiscuous(sContext, true); |
| |
| // stop timer |
| otPlatAlarmStop(sContext); |
| |
| // start to listen on the default channel |
| SuccessOrExit(error = otPlatRadioReceive(sContext, sChannel)); |
| |
| // enable diagnostics mode |
| otPlatDiagModeSet(true); |
| |
| memset(&sStats, 0, sizeof(struct DiagStats)); |
| |
| snprintf(aOutput, aOutputMaxLen, "start diagnostics mode\r\nstatus 0x%02x\r\n", error); |
| |
| exit: |
| OT_UNUSED_VARIABLE(argc); |
| OT_UNUSED_VARIABLE(argv); |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessStop(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| |
| otPlatAlarmStop(sContext); |
| otPlatDiagModeSet(false); |
| otPlatRadioSetPromiscuous(sContext, false); |
| |
| snprintf(aOutput, aOutputMaxLen, "received packets: %d\r\nsent packets: %d\r\nfirst received packet: rssi=%d, lqi=%d\r\n\nstop diagnostics mode\r\nstatus 0x%02x\r\n", |
| static_cast<int>(sStats.received_packets), static_cast<int>(sStats.sent_packets), static_cast<int>(sStats.first_rssi), |
| static_cast<int>(sStats.first_lqi), error); |
| |
| exit: |
| OT_UNUSED_VARIABLE(argc); |
| OT_UNUSED_VARIABLE(argv); |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| otError Diag::ParseLong(char *argv, long &value) |
| { |
| char *endptr; |
| value = strtol(argv, &endptr, 0); |
| return (*endptr == '\0') ? OT_ERROR_NONE : OT_ERROR_PARSE; |
| } |
| |
| void Diag::TxPacket() |
| { |
| sTxPacket->mLength = sTxLen; |
| sTxPacket->mChannel = sChannel; |
| sTxPacket->mPower = sTxPower; |
| |
| for (uint8_t i = 0; i < sTxLen; i++) |
| { |
| sTxPacket->mPsdu[i] = i; |
| } |
| otPlatRadioTransmit(sContext, sTxPacket); |
| } |
| |
| void Diag::ProcessChannel(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| |
| if (argc == 0) |
| { |
| snprintf(aOutput, aOutputMaxLen, "channel: %d\r\n", sChannel); |
| } |
| else |
| { |
| long value; |
| |
| SuccessOrExit(error = ParseLong(argv[0], value)); |
| VerifyOrExit(value >= OT_RADIO_CHANNEL_MIN && value <= OT_RADIO_CHANNEL_MAX, error = OT_ERROR_INVALID_ARGS); |
| sChannel = static_cast<uint8_t>(value); |
| |
| // listen on the set channel immediately |
| otPlatRadioReceive(sContext, sChannel); |
| otPlatDiagChannelSet(sChannel); |
| snprintf(aOutput, aOutputMaxLen, "set channel to %d\r\nstatus 0x%02x\r\n", sChannel, error); |
| } |
| |
| exit: |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessPower(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| |
| if (argc == 0) |
| { |
| snprintf(aOutput, aOutputMaxLen, "tx power: %d dBm\r\n", sTxPower); |
| } |
| else |
| { |
| long value; |
| |
| SuccessOrExit(error = ParseLong(argv[0], value)); |
| sTxPower = static_cast<int8_t>(value); |
| otPlatDiagTxPowerSet(sTxPower); |
| snprintf(aOutput, aOutputMaxLen, "set tx power to %d dBm\r\nstatus 0x%02x\r\n", sTxPower, error); |
| } |
| |
| exit: |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessSend(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| long value; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| VerifyOrExit(argc == 2, error = OT_ERROR_INVALID_ARGS); |
| |
| SuccessOrExit(error = ParseLong(argv[0], value)); |
| sTxPackets = static_cast<uint32_t>(value); |
| |
| SuccessOrExit(error = ParseLong(argv[1], value)); |
| VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_INVALID_ARGS); |
| sTxLen = static_cast<uint8_t>(value); |
| |
| snprintf(aOutput, aOutputMaxLen, "sending %#x packet(s), length %#x\r\nstatus 0x%02x\r\n", static_cast<int>(sTxPackets), static_cast<int>(sTxLen), error); |
| TxPacket(); |
| |
| exit: |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessRepeat(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| VerifyOrExit(argc > 0, error = OT_ERROR_INVALID_ARGS); |
| |
| if (strcmp(argv[0], "stop") == 0) |
| { |
| otPlatAlarmStop(sContext); |
| sRepeatActive = false; |
| snprintf(aOutput, aOutputMaxLen, "repeated packet transmission is stopped\r\nstatus 0x%02x\r\n", error); |
| } |
| else |
| { |
| long value; |
| |
| VerifyOrExit(argc == 2, error = OT_ERROR_INVALID_ARGS); |
| |
| SuccessOrExit(error = ParseLong(argv[0], value)); |
| sTxPeriod = static_cast<uint32_t>(value); |
| |
| SuccessOrExit(error = ParseLong(argv[1], value)); |
| VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_INVALID_ARGS); |
| sTxLen = static_cast<uint8_t>(value); |
| |
| sRepeatActive = true; |
| uint32_t now = otPlatAlarmGetNow(); |
| otPlatAlarmStartAt(sContext, now, sTxPeriod); |
| snprintf(aOutput, aOutputMaxLen, "sending packets of length %#x at the delay of %#x ms\r\nstatus 0x%02x\r\n", static_cast<int>(sTxLen), static_cast<int>(sTxPeriod), error); |
| } |
| |
| exit: |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessSleep(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| |
| otPlatRadioSleep(sContext); |
| snprintf(aOutput, aOutputMaxLen, "sleeping now...\r\n"); |
| |
| exit: |
| OT_UNUSED_VARIABLE(argc); |
| OT_UNUSED_VARIABLE(argv); |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::ProcessStats(int argc, char *argv[], char *aOutput, size_t aOutputMaxLen) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); |
| |
| snprintf(aOutput, aOutputMaxLen, "received packets: %d\r\nsent packets: %d\r\nfirst received packet: rssi=%d, lqi=%d\r\n", |
| static_cast<int>(sStats.received_packets), static_cast<int>(sStats.sent_packets), |
| static_cast<int>(sStats.first_rssi), static_cast<int>(sStats.first_lqi)); |
| |
| exit: |
| OT_UNUSED_VARIABLE(argc); |
| OT_UNUSED_VARIABLE(argv); |
| AppendErrorResult(error, aOutput, aOutputMaxLen); |
| } |
| |
| void Diag::DiagTransmitDone(otInstance *aInstance, otError aError) |
| { |
| OT_UNUSED_VARIABLE(aInstance); |
| if (aError == OT_ERROR_NONE) |
| { |
| sStats.sent_packets++; |
| |
| if (sTxPackets > 1) |
| { |
| sTxPackets--; |
| TxPacket(); |
| } |
| } |
| else |
| { |
| TxPacket(); |
| } |
| } |
| |
| void Diag::DiagReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError) |
| { |
| OT_UNUSED_VARIABLE(aInstance); |
| if (aError == OT_ERROR_NONE) |
| { |
| // for sensitivity test, only record the rssi and lqi for the first packet |
| if (sStats.received_packets == 0) |
| { |
| sStats.first_rssi = aFrame->mPower; |
| sStats.first_lqi = aFrame->mLqi; |
| } |
| |
| sStats.received_packets++; |
| } |
| otPlatDiagRadioReceived(aInstance, aFrame, aError); |
| otPlatRadioReceive(aInstance, sChannel); |
| } |
| |
| void Diag::AlarmFired(otInstance *aInstance) |
| { |
| if(sRepeatActive) |
| { |
| uint32_t now = otPlatAlarmGetNow(); |
| |
| TxPacket(); |
| otPlatAlarmStartAt(aInstance, now, sTxPeriod); |
| } |
| else |
| { |
| otPlatDiagAlarmCallback(aInstance); |
| } |
| } |
| |
| extern "C" void otPlatDiagAlarmFired(otInstance *aInstance) |
| { |
| Diag::AlarmFired(aInstance); |
| } |
| |
| extern "C" void otPlatDiagRadioTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError) |
| { |
| OT_UNUSED_VARIABLE(aFrame); |
| |
| Diag::DiagTransmitDone(aInstance, aError); |
| } |
| |
| extern "C" void otPlatDiagRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError) |
| { |
| Diag::DiagReceiveDone(aInstance, aFrame, aError); |
| } |
| |
| } // namespace Diagnostics |
| } // namespace ot |