C++程序  |  499行  |  16.56 KB

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <getopt.h>
#include <signal.h>

#include <cstdlib>
#include <cstring>
#include <memory>
#include <sstream>
#include <tuple>
#include <vector>

#include "contexthub.h"
#include "log.h"

#ifdef __ANDROID__
#include "androidcontexthub.h"
#else
#include "cp2130.h"
#include "usbcontext.h"
#include "usbcontexthub.h"
#endif

using namespace android;

enum class NanotoolCommand {
    Invalid,
    Disable,
    DisableAll,
    Calibrate,
    Test,
    Read,
    Poll,
    LoadCalibration,
    Flash,
    GetBridgeVer,
};

struct ParsedArgs {
    NanotoolCommand command = NanotoolCommand::Poll;
    std::vector<SensorSpec> sensors;
    int count = 0;
    bool logging_enabled = false;
    std::string filename;
    int device_index = 0;
};

static NanotoolCommand StrToCommand(const char *command_name) {
    static const std::vector<std::tuple<std::string, NanotoolCommand>> cmds = {
        std::make_tuple("disable",     NanotoolCommand::Disable),
        std::make_tuple("disable_all", NanotoolCommand::DisableAll),
        std::make_tuple("calibrate",   NanotoolCommand::Calibrate),
        std::make_tuple("cal",         NanotoolCommand::Calibrate),
        std::make_tuple("test",        NanotoolCommand::Test),
        std::make_tuple("read",        NanotoolCommand::Read),
        std::make_tuple("poll",        NanotoolCommand::Poll),
        std::make_tuple("load_cal",    NanotoolCommand::LoadCalibration),
        std::make_tuple("flash",       NanotoolCommand::Flash),
        std::make_tuple("bridge_ver",  NanotoolCommand::GetBridgeVer),
    };

    if (!command_name) {
        return NanotoolCommand::Invalid;
    }

    for (size_t i = 0; i < cmds.size(); i++) {
        std::string name;
        NanotoolCommand cmd;

        std::tie(name, cmd) = cmds[i];
        if (name.compare(command_name) == 0) {
            return cmd;
        }
    }

    return NanotoolCommand::Invalid;
}

static void PrintUsage(const char *name) {
    const char *help_text =
        "options:\n"
        "  -x, --cmd          Argument must be one of:\n"
        "                        bridge_ver: retrieve bridge version information (not\n"
        "                           supported on all devices)\n"
        "                        disable: send a disable request for one sensor\n"
        "                        disable_all: send a disable request for all sensors\n"
        "                        calibrate: disable the sensor, then perform the sensor\n"
        "                           calibration routine\n"
        "                        test: run a sensor's self-test routine\n"
#ifndef __ANDROID__
        "                        flash: load a new firmware image to the hub\n"
#endif
        "                        load_cal: send data from calibration file to hub\n"
        "                        poll (default): enable the sensor, output received\n"
        "                           events, then disable the sensor before exiting\n"
        "                        read: output events for the given sensor, or all events\n"
        "                           if no sensor specified\n"
        "\n"
        "  -s, --sensor       Specify sensor type, and parameters for the command.\n"
        "                     Format is sensor_type[:rate[:latency_ms]][=cal_ref].\n"
        "                     See below for a complete list sensor types. A rate is\n"
        "                     required when enabling a sensor, but latency is optional\n"
        "                     and defaults to 0. Rate can be specified in Hz, or as one\n"
        "                     of the special values \"onchange\", \"ondemand\", or\n"
        "                     \"oneshot\".\n"
        "                     Some sensors require a ground truth value for calibration.\n"
        "                     Use the cal_ref parameter for this purpose (it's parsed as\n"
        "                     a float).\n"
        "                     This argument can be repeated to perform a command on\n"
        "                     multiple sensors.\n"
        "\n"
        "  -c, --count        Number of samples to read before exiting, or set to 0 to\n"
        "                     read indefinitely (the default behavior)\n"
        "\n"
        "  -f, --file\n"
        "                     Specifies the file to be used with flash.\n"
        "\n"
        "  -l, --log          Outputs logs from the sensor hub as they become available.\n"
        "                     The logs will be printed inline with sensor samples.\n"
        "                     The default is for log messages to be ignored.\n"
#ifndef __ANDROID__
        // This option is only applicable when connecting over USB
        "\n"
        "  -i, --index        Selects the device to work with by specifying the index\n"
        "                     into the device list (default: 0)\n"
#endif
        "\n"
        "  -v, -vv            Output verbose/extra verbose debugging information\n";

    fprintf(stderr, "%s %s\n\n", name, NANOTOOL_VERSION_STR);
    fprintf(stderr, "Usage: %s [options]\n\n%s\n", name, help_text);
    fprintf(stderr, "Supported sensors: %s\n\n",
            ContextHub::ListAllSensorAbbrevNames().c_str());
    fprintf(stderr, "Examples:\n"
                    "  %s -s accel:50\n"
                    "  %s -s accel:50:1000 -s gyro:50:1000\n"
                    "  %s -s prox:onchange\n"
                    "  %s -x calibrate -s baro=1000\n",
            name, name, name, name);
}

/*
 * Performs higher-level argument validation beyond just parsing the parameters,
 * for example check whether a required argument is present when the command is
 * set to a specific value.
 */
static bool ValidateArgs(std::unique_ptr<ParsedArgs>& args, const char *name) {
    if (!args->sensors.size()
          && (args->command == NanotoolCommand::Disable
                || args->command == NanotoolCommand::Calibrate
                || args->command == NanotoolCommand::Test
                || args->command == NanotoolCommand::Poll)) {
        fprintf(stderr, "%s: At least 1 sensor must be specified for this "
                        "command (use -s)\n",
                name);
        return false;
    }

    if (args->command == NanotoolCommand::Flash
            && args->filename.empty()) {
        fprintf(stderr, "%s: A filename must be specified for this command "
                        "(use -f)\n",
                name);
        return false;
    }

    if (args->command == NanotoolCommand::Poll) {
        for (unsigned int i = 0; i < args->sensors.size(); i++) {
            if (args->sensors[i].special_rate == SensorSpecialRate::None
                  && args->sensors[i].rate_hz < 0) {
                fprintf(stderr, "%s: Sample rate must be specified for sensor "
                        "%s\n", name,
                        ContextHub::SensorTypeToAbbrevName(
                            args->sensors[i].sensor_type).c_str());
                return false;
            }
        }
    }

    if (args->command == NanotoolCommand::Calibrate) {
        for (unsigned int i = 0; i < args->sensors.size(); i++) {
            if (!args->sensors[i].have_cal_ref
                  && (args->sensors[i].sensor_type == SensorType::Barometer
                        || args->sensors[i].sensor_type ==
                             SensorType::AmbientLightSensor)) {
                fprintf(stderr, "%s: Calibration reference required for sensor "
                                "%s (for example: -s baro=1000)\n", name,
                        ContextHub::SensorTypeToAbbrevName(
                            args->sensors[i].sensor_type).c_str());
                return false;
            }
        }
    }

    return true;
}

static bool ParseRate(const std::string& param, SensorSpec& spec) {
    static const std::vector<std::tuple<std::string, SensorSpecialRate>> rates = {
        std::make_tuple("ondemand", SensorSpecialRate::OnDemand),
        std::make_tuple("onchange", SensorSpecialRate::OnChange),
        std::make_tuple("oneshot",  SensorSpecialRate::OneShot),
    };

    for (size_t i = 0; i < rates.size(); i++) {
        std::string name;
        SensorSpecialRate rate;

        std::tie(name, rate) = rates[i];
        if (param == name) {
            spec.special_rate = rate;
            return true;
        }
    }

    spec.rate_hz = std::stof(param);
    if (spec.rate_hz < 0) {
        return false;
    }

    return true;
}

// Parse a sensor argument in the form of "sensor_name[:rate[:latency]][=cal_ref]"
// into a SensorSpec, and add it to ParsedArgs.
static bool ParseSensorArg(std::vector<SensorSpec>& sensors, const char *arg_str,
        const char *name) {
    SensorSpec spec;
    std::string param;
    std::string pre_cal_ref;
    std::stringstream full_arg_ss(arg_str);
    unsigned int index = 0;

    while (std::getline(full_arg_ss, param, '=')) {
        if (index == 0) {
            pre_cal_ref = param;
        } else if (index == 1) {
            spec.cal_ref = std::stof(param);
            spec.have_cal_ref = true;
        } else {
            fprintf(stderr, "%s: Only one calibration reference may be "
                            "supplied\n", name);
            return false;
        }
        index++;
    }

    index = 0;
    std::stringstream pre_cal_ref_ss(pre_cal_ref);
    while (std::getline(pre_cal_ref_ss, param, ':')) {
        if (index == 0) { // Parse sensor type
            spec.sensor_type = ContextHub::SensorAbbrevNameToType(param);
            if (spec.sensor_type == SensorType::Invalid_) {
                fprintf(stderr, "%s: Invalid sensor name '%s'\n",
                        name, param.c_str());
                return false;
            }
        } else if (index == 1) { // Parse sample rate
            if (!ParseRate(param, spec)) {
                fprintf(stderr, "%s: Invalid sample rate %s\n", name,
                        param.c_str());
                return false;
            }
        } else if (index == 2) { // Parse latency
            long long latency_ms = std::stoll(param);
            if (latency_ms < 0) {
                fprintf(stderr, "%s: Invalid latency %lld\n", name, latency_ms);
                return false;
            }
            spec.latency_ns = static_cast<uint64_t>(latency_ms) * 1000000;
        } else {
            fprintf(stderr, "%s: Too many arguments in -s", name);
            return false;
        }
        index++;
    }

    sensors.push_back(spec);
    return true;
}

static std::unique_ptr<ParsedArgs> ParseArgs(int argc, char **argv) {
    static const struct option long_opts[] = {
        {"cmd",     required_argument, nullptr, 'x'},
        {"sensor",  required_argument, nullptr, 's'},
        {"count",   required_argument, nullptr, 'c'},
        {"flash",   required_argument, nullptr, 'f'},
        {"log",     no_argument,       nullptr, 'l'},
        {"index",   required_argument, nullptr, 'i'},
        {}  // Indicates the end of the option list
    };

    auto args = std::unique_ptr<ParsedArgs>(new ParsedArgs());
    int index = 0;
    while (42) {
        int c = getopt_long(argc, argv, "x:s:c:f:v::li:", long_opts, &index);
        if (c == -1) {
            break;
        }

        switch (c) {
          case 'x': {
            args->command = StrToCommand(optarg);
            if (args->command == NanotoolCommand::Invalid) {
                fprintf(stderr, "%s: Invalid command '%s'\n", argv[0], optarg);
                return nullptr;
            }
            break;
          }
          case 's': {
            if (!ParseSensorArg(args->sensors, optarg, argv[0])) {
                return nullptr;
            }
            break;
          }
          case 'c': {
            args->count = atoi(optarg);
            if (args->count < 0) {
                fprintf(stderr, "%s: Invalid sample count %d\n",
                        argv[0], args->count);
                return nullptr;
            }
            break;
          }
          case 'v': {
            if (optarg && optarg[0] == 'v') {
                Log::SetLevel(Log::LogLevel::Debug);
            } else {
                Log::SetLevel(Log::LogLevel::Info);
            }
            break;
          }
          case 'l': {
            args->logging_enabled = true;
            break;
          }
          case 'f': {
            if (optarg) {
                args->filename = std::string(optarg);
            } else {
                fprintf(stderr, "File requires a filename\n");
                return nullptr;
            }
            break;
          }
          case 'i': {
            args->device_index = atoi(optarg);
            if (args->device_index < 0) {
                fprintf(stderr, "%s: Invalid device index %d\n", argv[0],
                        args->device_index);
                return nullptr;
            }
            break;
          }
          default:
            return nullptr;
        }
    }

    if (!ValidateArgs(args, argv[0])) {
        return nullptr;
    }
    return args;
}

static std::unique_ptr<ContextHub> GetContextHub(std::unique_ptr<ParsedArgs>& args) {
#ifdef __ANDROID__
    (void) args;
    return std::unique_ptr<AndroidContextHub>(new AndroidContextHub());
#else
    return std::unique_ptr<UsbContextHub>(new UsbContextHub(args->device_index));
#endif
}

#ifdef __ANDROID__
static void SignalHandler(int sig) {
    // Catches a signal and does nothing, to allow any pending syscalls to be
    // exited with SIGINT and normal cleanup to occur. If SIGINT is sent a
    // second time, the system will invoke the standard handler.
    (void) sig;
}

static void TerminateHandler() {
    AndroidContextHub::TerminateHandler();
    std::abort();
}

static void SetHandlers() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = SignalHandler;
    sigaction(SIGINT, &sa, NULL);

    std::set_terminate(TerminateHandler);
}
#endif

int main(int argc, char **argv) {
    Log::Initialize(new PrintfLogger(), Log::LogLevel::Warn);

    // If no arguments given, print usage without any error messages
    if (argc == 1) {
        PrintUsage(argv[0]);
        return 1;
    }

    std::unique_ptr<ParsedArgs> args = ParseArgs(argc, argv);
    if (!args) {
        PrintUsage(argv[0]);
        return 1;
    }

#ifdef __ANDROID__
    SetHandlers();
#endif

    std::unique_ptr<ContextHub> hub = GetContextHub(args);
    if (!hub || !hub->Initialize()) {
        LOGE("Error initializing ContextHub");
        return -1;
    }

    hub->SetLoggingEnabled(args->logging_enabled);

    bool success = true;
    switch (args->command) {
      case NanotoolCommand::Disable:
        success = hub->DisableSensors(args->sensors);
        break;
      case NanotoolCommand::DisableAll:
        success = hub->DisableAllSensors();
        break;
      case NanotoolCommand::Read: {
        if (!args->sensors.size()) {
            hub->PrintAllEvents(args->count);
        } else {
            hub->PrintSensorEvents(args->sensors, args->count);
        }
        break;
      }
      case NanotoolCommand::Poll: {
        success = hub->EnableSensors(args->sensors);
        if (success) {
            hub->PrintSensorEvents(args->sensors, args->count);
        }
        break;
      }
      case NanotoolCommand::Calibrate: {
        hub->DisableSensors(args->sensors);
        success = hub->CalibrateSensors(args->sensors);
        break;
      }
      case NanotoolCommand::Test: {
        hub->DisableSensors(args->sensors);
        success = hub->TestSensors(args->sensors);
        break;
      }
      case NanotoolCommand::LoadCalibration: {
        success = hub->LoadCalibration();
        break;
      }
      case NanotoolCommand::Flash: {
        success = hub->Flash(args->filename);
        break;
      }
      case NanotoolCommand::GetBridgeVer: {
        success = hub->PrintBridgeVersion();
        break;
      }
      default:
        LOGE("Command not implemented");
        return 1;
    }

    if (!success) {
        LOGE("Command failed");
        return -1;
    } else if (args->command != NanotoolCommand::Read
                   && args->command != NanotoolCommand::Poll) {
        printf("Operation completed successfully\n");
    }

    return 0;
}