/* * 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; }