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
StrToCommand(const char * command_name)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
PrintUsage(const char * name)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 */
ValidateArgs(std::unique_ptr<ParsedArgs> & args,const char * name)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
ParseRate(const std::string & param,SensorSpec & spec)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.
ParseSensorArg(std::vector<SensorSpec> & sensors,const char * arg_str,const char * name)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
ParseArgs(int argc,char ** argv)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
GetContextHub(std::unique_ptr<ParsedArgs> & args)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__
SignalHandler(int sig)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
TerminateHandler()397 static void TerminateHandler() {
398 AndroidContextHub::TerminateHandler();
399 std::abort();
400 }
401
SetHandlers()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
main(int argc,char ** argv)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