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