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