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