Home | History | Annotate | Download | only in nanotool
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <getopt.h>
     18 #include <signal.h>
     19 
     20 #include <cstdlib>
     21 #include <cstring>
     22 #include <memory>
     23 #include <sstream>
     24 #include <tuple>
     25 #include <vector>
     26 
     27 #include "contexthub.h"
     28 #include "log.h"
     29 
     30 #ifdef __ANDROID__
     31 #include "androidcontexthub.h"
     32 #else
     33 #include "cp2130.h"
     34 #include "usbcontext.h"
     35 #include "usbcontexthub.h"
     36 #endif
     37 
     38 using namespace android;
     39 
     40 enum class NanotoolCommand {
     41     Invalid,
     42     Disable,
     43     DisableAll,
     44     Calibrate,
     45     Read,
     46     Poll,
     47     LoadCalibration,
     48     Flash,
     49 };
     50 
     51 struct ParsedArgs {
     52     NanotoolCommand command = NanotoolCommand::Poll;
     53     std::vector<SensorSpec> sensors;
     54     int count = 0;
     55     bool logging_enabled = false;
     56     std::string filename;
     57     int device_index = 0;
     58 };
     59 
     60 static NanotoolCommand StrToCommand(const char *command_name) {
     61     static const std::vector<std::tuple<std::string, NanotoolCommand>> cmds = {
     62         std::make_tuple("disable",     NanotoolCommand::Disable),
     63         std::make_tuple("disable_all", NanotoolCommand::DisableAll),
     64         std::make_tuple("calibrate",   NanotoolCommand::Calibrate),
     65         std::make_tuple("cal",         NanotoolCommand::Calibrate),
     66         std::make_tuple("read",        NanotoolCommand::Read),
     67         std::make_tuple("poll",        NanotoolCommand::Poll),
     68         std::make_tuple("load_cal",    NanotoolCommand::LoadCalibration),
     69         std::make_tuple("flash",       NanotoolCommand::Flash),
     70     };
     71 
     72     if (!command_name) {
     73         return NanotoolCommand::Invalid;
     74     }
     75 
     76     for (size_t i = 0; i < cmds.size(); i++) {
     77         std::string name;
     78         NanotoolCommand cmd;
     79 
     80         std::tie(name, cmd) = cmds[i];
     81         if (name.compare(command_name) == 0) {
     82             return cmd;
     83         }
     84     }
     85 
     86     return NanotoolCommand::Invalid;
     87 }
     88 
     89 static void PrintUsage(const char *name) {
     90     const char *help_text =
     91         "options:\n"
     92         "  -x, --cmd          Argument must be one of:\n"
     93         "                        disable: send a disable request for one sensor\n"
     94         "                        disable_all: send a disable request for all sensors\n"
     95         "                        calibrate: disable the sensor, then perform the sensor\n"
     96         "                           calibration routine\n"
     97         "                        load_cal: send data from calibration file to hub\n"
     98         "                        read: output events for the given sensor, or all events\n"
     99         "                           if no sensor specified\n"
    100         "                        poll (default): enable the sensor, output received\n"
    101         "                           events, then disable the sensor before exiting\n"
    102         "                        flash: Load a new firmware image to the hub\n"
    103         "\n"
    104         "  -s, --sensor       Specify sensor type, and parameters for the command.\n"
    105         "                     Format is sensor_type[:rate[:latency_ms]][=cal_ref].\n"
    106         "                     See below for a complete list sensor types. A rate is\n"
    107         "                     required when enabling a sensor, but latency is optional\n"
    108         "                     and defaults to 0. Rate can be specified in Hz, or as one\n"
    109         "                     of the special values \"onchange\", \"ondemand\", or\n"
    110         "                     \"oneshot\".\n"
    111         "                     Some sensors require a ground truth value for calibration.\n"
    112         "                     Use the cal_ref parameter for this purpose (it's parsed as\n"
    113         "                     a float).\n"
    114         "                     This argument can be repeated to perform a command on\n"
    115         "                     multiple sensors.\n"
    116         "\n"
    117         "  -c, --count        Number of samples to read before exiting, or set to 0 to\n"
    118         "                     read indefinitely (the default behavior)\n"
    119         "\n"
    120         "  -f, --file\n"
    121         "                     Specifies the file to be used with flash.\n"
    122         "\n"
    123         "  -l, --log          Outputs logs from the sensor hub as they become available.\n"
    124         "                     The logs will be printed inline with sensor samples.\n"
    125         "                     The default is for log messages to be ignored.\n"
    126 #ifndef __ANDROID__
    127         // This option is only applicable when connecting over USB
    128         "\n"
    129         "  -i, --index        Selects the device to work with by specifying the index\n"
    130         "                     into the device list (default: 0)\n"
    131 #endif
    132         "\n"
    133         "  -v, -vv            Output verbose/extra verbose debugging information\n";
    134 
    135     fprintf(stderr, "%s %s\n\n", name, NANOTOOL_VERSION_STR);
    136     fprintf(stderr, "Usage: %s [options]\n\n%s\n", name, help_text);
    137     fprintf(stderr, "Supported sensors: %s\n\n",
    138             ContextHub::ListAllSensorAbbrevNames().c_str());
    139     fprintf(stderr, "Examples:\n"
    140                     "  %s -s accel:50\n"
    141                     "  %s -s accel:50:1000 -s gyro:50:1000\n"
    142                     "  %s -s prox:onchange\n"
    143                     "  %s -x calibrate -s baro=1000\n",
    144             name, name, name, name);
    145 }
    146 
    147 /*
    148  * Performs higher-level argument validation beyond just parsing the parameters,
    149  * for example check whether a required argument is present when the command is
    150  * set to a specific value.
    151  */
    152 static bool ValidateArgs(std::unique_ptr<ParsedArgs>& args, const char *name) {
    153     if (!args->sensors.size()
    154           && (args->command == NanotoolCommand::Disable
    155                 || args->command == NanotoolCommand::Calibrate
    156                 || args->command == NanotoolCommand::Poll)) {
    157         fprintf(stderr, "%s: At least 1 sensor must be specified for this "
    158                         "command (use -s)\n",
    159                 name);
    160         return false;
    161     }
    162 
    163     if (args->command == NanotoolCommand::Flash
    164             && args->filename.empty()) {
    165         fprintf(stderr, "%s: A filename must be specified for this command "
    166                         "(use -f)\n",
    167                 name);
    168         return false;
    169     }
    170 
    171     if (args->command == NanotoolCommand::Poll) {
    172         for (unsigned int i = 0; i < args->sensors.size(); i++) {
    173             if (args->sensors[i].special_rate == SensorSpecialRate::None
    174                   && args->sensors[i].rate_hz < 0) {
    175                 fprintf(stderr, "%s: Sample rate must be specified for sensor "
    176                         "%s\n", name,
    177                         ContextHub::SensorTypeToAbbrevName(
    178                             args->sensors[i].sensor_type).c_str());
    179                 return false;
    180             }
    181         }
    182     }
    183 
    184     if (args->command == NanotoolCommand::Calibrate) {
    185         for (unsigned int i = 0; i < args->sensors.size(); i++) {
    186             if (!args->sensors[i].have_cal_ref
    187                   && (args->sensors[i].sensor_type == SensorType::Barometer
    188                         || args->sensors[i].sensor_type ==
    189                              SensorType::AmbientLightSensor)) {
    190                 fprintf(stderr, "%s: Calibration reference required for sensor "
    191                                 "%s (for example: -s baro=1000)\n", name,
    192                         ContextHub::SensorTypeToAbbrevName(
    193                             args->sensors[i].sensor_type).c_str());
    194                 return false;
    195             }
    196         }
    197     }
    198 
    199     return true;
    200 }
    201 
    202 static bool ParseRate(const std::string& param, SensorSpec& spec) {
    203     static const std::vector<std::tuple<std::string, SensorSpecialRate>> rates = {
    204         std::make_tuple("ondemand", SensorSpecialRate::OnDemand),
    205         std::make_tuple("onchange", SensorSpecialRate::OnChange),
    206         std::make_tuple("oneshot",  SensorSpecialRate::OneShot),
    207     };
    208 
    209     for (size_t i = 0; i < rates.size(); i++) {
    210         std::string name;
    211         SensorSpecialRate rate;
    212 
    213         std::tie(name, rate) = rates[i];
    214         if (param == name) {
    215             spec.special_rate = rate;
    216             return true;
    217         }
    218     }
    219 
    220     spec.rate_hz = std::stof(param);
    221     if (spec.rate_hz < 0) {
    222         return false;
    223     }
    224 
    225     return true;
    226 }
    227 
    228 // Parse a sensor argument in the form of "sensor_name[:rate[:latency]][=cal_ref]"
    229 // into a SensorSpec, and add it to ParsedArgs.
    230 static bool ParseSensorArg(std::vector<SensorSpec>& sensors, const char *arg_str,
    231         const char *name) {
    232     SensorSpec spec;
    233     std::string param;
    234     std::string pre_cal_ref;
    235     std::stringstream full_arg_ss(arg_str);
    236     unsigned int index = 0;
    237 
    238     while (std::getline(full_arg_ss, param, '=')) {
    239         if (index == 0) {
    240             pre_cal_ref = param;
    241         } else if (index == 1) {
    242             spec.cal_ref = std::stof(param);
    243             spec.have_cal_ref = true;
    244         } else {
    245             fprintf(stderr, "%s: Only one calibration reference may be "
    246                             "supplied\n", name);
    247             return false;
    248         }
    249         index++;
    250     }
    251 
    252     index = 0;
    253     std::stringstream pre_cal_ref_ss(pre_cal_ref);
    254     while (std::getline(pre_cal_ref_ss, param, ':')) {
    255         if (index == 0) { // Parse sensor type
    256             spec.sensor_type = ContextHub::SensorAbbrevNameToType(param);
    257             if (spec.sensor_type == SensorType::Invalid_) {
    258                 fprintf(stderr, "%s: Invalid sensor name '%s'\n",
    259                         name, param.c_str());
    260                 return false;
    261             }
    262         } else if (index == 1) { // Parse sample rate
    263             if (!ParseRate(param, spec)) {
    264                 fprintf(stderr, "%s: Invalid sample rate %s\n", name,
    265                         param.c_str());
    266                 return false;
    267             }
    268         } else if (index == 2) { // Parse latency
    269             long long latency_ms = std::stoll(param);
    270             if (latency_ms < 0) {
    271                 fprintf(stderr, "%s: Invalid latency %lld\n", name, latency_ms);
    272                 return false;
    273             }
    274             spec.latency_ns = static_cast<uint64_t>(latency_ms) * 1000000;
    275         } else {
    276             fprintf(stderr, "%s: Too many arguments in -s", name);
    277             return false;
    278         }
    279         index++;
    280     }
    281 
    282     sensors.push_back(spec);
    283     return true;
    284 }
    285 
    286 static std::unique_ptr<ParsedArgs> ParseArgs(int argc, char **argv) {
    287     static const struct option long_opts[] = {
    288         {"cmd",     required_argument, nullptr, 'x'},
    289         {"sensor",  required_argument, nullptr, 's'},
    290         {"count",   required_argument, nullptr, 'c'},
    291         {"flash",   required_argument, nullptr, 'f'},
    292         {"log",     no_argument,       nullptr, 'l'},
    293         {"index",   required_argument, nullptr, 'i'},
    294     };
    295 
    296     auto args = std::unique_ptr<ParsedArgs>(new ParsedArgs());
    297     int index = 0;
    298     while (42) {
    299         int c = getopt_long(argc, argv, "x:s:c:f:v::li:", long_opts, &index);
    300         if (c == -1) {
    301             break;
    302         }
    303 
    304         switch (c) {
    305           case 'x': {
    306             args->command = StrToCommand(optarg);
    307             if (args->command == NanotoolCommand::Invalid) {
    308                 fprintf(stderr, "%s: Invalid command '%s'\n", argv[0], optarg);
    309                 return nullptr;
    310             }
    311             break;
    312           }
    313           case 's': {
    314             if (!ParseSensorArg(args->sensors, optarg, argv[0])) {
    315                 return nullptr;
    316             }
    317             break;
    318           }
    319           case 'c': {
    320             args->count = atoi(optarg);
    321             if (args->count < 0) {
    322                 fprintf(stderr, "%s: Invalid sample count %d\n",
    323                         argv[0], args->count);
    324                 return nullptr;
    325             }
    326             break;
    327           }
    328           case 'v': {
    329             if (optarg && optarg[0] == 'v') {
    330                 Log::SetLevel(Log::LogLevel::Debug);
    331             } else {
    332                 Log::SetLevel(Log::LogLevel::Info);
    333             }
    334             break;
    335           }
    336           case 'l': {
    337             args->logging_enabled = true;
    338             break;
    339           }
    340           case 'f': {
    341             if (optarg) {
    342                 args->filename = std::string(optarg);
    343             } else {
    344                 fprintf(stderr, "File requires a filename\n");
    345                 return nullptr;
    346             }
    347             break;
    348           }
    349           case 'i': {
    350             args->device_index = atoi(optarg);
    351             if (args->device_index < 0) {
    352                 fprintf(stderr, "%s: Invalid device index %d\n", argv[0],
    353                         args->device_index);
    354                 return nullptr;
    355             }
    356             break;
    357           }
    358           default:
    359             return nullptr;
    360         }
    361     }
    362 
    363     if (!ValidateArgs(args, argv[0])) {
    364         return nullptr;
    365     }
    366     return args;
    367 }
    368 
    369 static std::unique_ptr<ContextHub> GetContextHub(std::unique_ptr<ParsedArgs>& args) {
    370 #ifdef __ANDROID__
    371     (void) args;
    372     return std::unique_ptr<AndroidContextHub>(new AndroidContextHub());
    373 #else
    374     return std::unique_ptr<UsbContextHub>(new UsbContextHub(args->device_index));
    375 #endif
    376 }
    377 
    378 #ifdef __ANDROID__
    379 static void SignalHandler(int sig) {
    380     // Catches a signal and does nothing, to allow any pending syscalls to be
    381     // exited with SIGINT and normal cleanup to occur. If SIGINT is sent a
    382     // second time, the system will invoke the standard handler.
    383     (void) sig;
    384 }
    385 
    386 static void TerminateHandler() {
    387     AndroidContextHub::TerminateHandler();
    388     std::abort();
    389 }
    390 
    391 static void SetHandlers() {
    392     struct sigaction sa;
    393     memset(&sa, 0, sizeof(sa));
    394     sa.sa_handler = SignalHandler;
    395     sigaction(SIGINT, &sa, NULL);
    396 
    397     std::set_terminate(TerminateHandler);
    398 }
    399 #endif
    400 
    401 int main(int argc, char **argv) {
    402     Log::Initialize(new PrintfLogger(), Log::LogLevel::Warn);
    403 
    404     // If no arguments given, print usage without any error messages
    405     if (argc == 1) {
    406         PrintUsage(argv[0]);
    407         return 1;
    408     }
    409 
    410     std::unique_ptr<ParsedArgs> args = ParseArgs(argc, argv);
    411     if (!args) {
    412         PrintUsage(argv[0]);
    413         return 1;
    414     }
    415 
    416 #ifdef __ANDROID__
    417     SetHandlers();
    418 #endif
    419 
    420     std::unique_ptr<ContextHub> hub = GetContextHub(args);
    421     if (!hub || !hub->Initialize()) {
    422         LOGE("Error initializing ContextHub");
    423         return -1;
    424     }
    425 
    426     hub->SetLoggingEnabled(args->logging_enabled);
    427 
    428     bool success = true;
    429     switch (args->command) {
    430       case NanotoolCommand::Disable:
    431         success = hub->DisableSensors(args->sensors);
    432         break;
    433       case NanotoolCommand::DisableAll:
    434         success = hub->DisableAllSensors();
    435         break;
    436       case NanotoolCommand::Read: {
    437         if (!args->sensors.size()) {
    438             hub->PrintAllEvents(args->count);
    439         } else {
    440             hub->PrintSensorEvents(args->sensors, args->count);
    441         }
    442         break;
    443       }
    444       case NanotoolCommand::Poll: {
    445         success = hub->EnableSensors(args->sensors);
    446         if (success) {
    447             hub->PrintSensorEvents(args->sensors, args->count);
    448         }
    449         break;
    450       }
    451       case NanotoolCommand::Calibrate: {
    452         hub->DisableSensors(args->sensors);
    453         success = hub->CalibrateSensors(args->sensors);
    454         break;
    455       }
    456       case NanotoolCommand::LoadCalibration: {
    457         success = hub->LoadCalibration();
    458         break;
    459       }
    460       case NanotoolCommand::Flash: {
    461         success = hub->Flash(args->filename);
    462         break;
    463       }
    464       default:
    465         LOGE("Command not implemented");
    466         return 1;
    467     }
    468 
    469     if (!success) {
    470         LOGE("Command failed");
    471         return -1;
    472     } else if (args->command != NanotoolCommand::Read
    473                    && args->command != NanotoolCommand::Poll) {
    474         printf("Operation completed successfully\n");
    475     }
    476 
    477     return 0;
    478 }
    479