infrared_cli.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. #include <cli/cli.h>
  2. #include <cli/cli_i.h>
  3. #include <infrared.h>
  4. #include <infrared_worker.h>
  5. #include <furi_hal_infrared.h>
  6. #include <flipper_format.h>
  7. #include <toolbox/args.h>
  8. #include "infrared_signal.h"
  9. #include "infrared_brute_force.h"
  10. #include "m-dict.h"
  11. #include "m-string.h"
  12. #define INFRARED_CLI_BUF_SIZE 10
  13. DICT_DEF2(dict_signals, string_t, STRING_OPLIST, int, M_DEFAULT_OPLIST)
  14. enum RemoteTypes { TV = 0, AC = 1 };
  15. static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args);
  16. static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args);
  17. static void infrared_cli_process_decode(Cli* cli, FuriString* args);
  18. static void infrared_cli_process_universal(Cli* cli, FuriString* args);
  19. static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type);
  20. static void
  21. infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal);
  22. static const struct {
  23. const char* cmd;
  24. void (*process_function)(Cli* cli, FuriString* args);
  25. } infrared_cli_commands[] = {
  26. {.cmd = "rx", .process_function = infrared_cli_start_ir_rx},
  27. {.cmd = "tx", .process_function = infrared_cli_start_ir_tx},
  28. {.cmd = "decode", .process_function = infrared_cli_process_decode},
  29. {.cmd = "universal", .process_function = infrared_cli_process_universal},
  30. };
  31. static void signal_received_callback(void* context, InfraredWorkerSignal* received_signal) {
  32. furi_assert(received_signal);
  33. char buf[100];
  34. size_t buf_cnt;
  35. Cli* cli = (Cli*)context;
  36. if(infrared_worker_signal_is_decoded(received_signal)) {
  37. const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
  38. buf_cnt = snprintf(
  39. buf,
  40. sizeof(buf),
  41. "%s, A:0x%0*lX, C:0x%0*lX%s\r\n",
  42. infrared_get_protocol_name(message->protocol),
  43. ROUND_UP_TO(infrared_get_protocol_address_length(message->protocol), 4),
  44. message->address,
  45. ROUND_UP_TO(infrared_get_protocol_command_length(message->protocol), 4),
  46. message->command,
  47. message->repeat ? " R" : "");
  48. cli_write(cli, (uint8_t*)buf, buf_cnt);
  49. } else {
  50. const uint32_t* timings;
  51. size_t timings_cnt;
  52. infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
  53. buf_cnt = snprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt);
  54. cli_write(cli, (uint8_t*)buf, buf_cnt);
  55. for(size_t i = 0; i < timings_cnt; ++i) {
  56. buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
  57. cli_write(cli, (uint8_t*)buf, buf_cnt);
  58. }
  59. buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
  60. cli_write(cli, (uint8_t*)buf, buf_cnt);
  61. }
  62. }
  63. static void infrared_cli_start_ir_rx(Cli* cli, FuriString* args) {
  64. UNUSED(cli);
  65. UNUSED(args);
  66. InfraredWorker* worker = infrared_worker_alloc();
  67. infrared_worker_rx_start(worker);
  68. infrared_worker_rx_set_received_signal_callback(worker, signal_received_callback, cli);
  69. printf("Receiving INFRARED...\r\nPress Ctrl+C to abort\r\n");
  70. while(!cli_cmd_interrupt_received(cli)) {
  71. furi_delay_ms(50);
  72. }
  73. infrared_worker_rx_stop(worker);
  74. infrared_worker_free(worker);
  75. }
  76. static void infrared_cli_print_usage(void) {
  77. printf("Usage:\r\n");
  78. printf("\tir rx\r\n");
  79. printf("\tir tx <protocol> <address> <command>\r\n");
  80. printf("\t<command> and <address> are hex-formatted\r\n");
  81. printf("\tAvailable protocols:");
  82. for(int i = 0; infrared_is_protocol_valid((InfraredProtocol)i); ++i) {
  83. printf(" %s", infrared_get_protocol_name((InfraredProtocol)i));
  84. }
  85. printf("\r\n");
  86. printf("\tRaw format:\r\n");
  87. printf("\tir tx RAW F:<frequency> DC:<duty_cycle> <sample0> <sample1>...\r\n");
  88. printf(
  89. "\tFrequency (%d - %d), Duty cycle (0 - 100), max 512 samples\r\n",
  90. INFRARED_MIN_FREQUENCY,
  91. INFRARED_MAX_FREQUENCY);
  92. printf("\tir decode <input_file> [<output_file>]\r\n");
  93. printf("\tir universal <tv, ac> <signal name>\r\n");
  94. printf("\tir universal list <tv, ac>\r\n");
  95. }
  96. static bool infrared_cli_parse_message(const char* str, InfraredSignal* signal) {
  97. char protocol_name[32];
  98. InfraredMessage message;
  99. int parsed = sscanf(str, "%31s %lX %lX", protocol_name, &message.address, &message.command);
  100. if(parsed != 3) {
  101. return false;
  102. }
  103. message.protocol = infrared_get_protocol_by_name(protocol_name);
  104. message.repeat = false;
  105. infrared_signal_set_message(signal, &message);
  106. return infrared_signal_is_valid(signal);
  107. }
  108. static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) {
  109. char frequency_str[INFRARED_CLI_BUF_SIZE];
  110. char duty_cycle_str[INFRARED_CLI_BUF_SIZE];
  111. int parsed = sscanf(str, "RAW F:%9s DC:%9s", frequency_str, duty_cycle_str);
  112. if(parsed != 2) {
  113. return false;
  114. }
  115. uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT);
  116. uint32_t frequency = atoi(frequency_str);
  117. float duty_cycle = (float)atoi(duty_cycle_str) / 100;
  118. str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE;
  119. size_t timings_size = 0;
  120. while(1) {
  121. while(*str == ' ') {
  122. ++str;
  123. }
  124. char timing_str[INFRARED_CLI_BUF_SIZE];
  125. if(sscanf(str, "%9s", timing_str) != 1) {
  126. break;
  127. }
  128. str += strlen(timing_str);
  129. uint32_t timing = atoi(timing_str);
  130. if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) {
  131. break;
  132. }
  133. timings[timings_size] = timing;
  134. ++timings_size;
  135. }
  136. infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle);
  137. free(timings);
  138. return infrared_signal_is_valid(signal);
  139. }
  140. static void infrared_cli_start_ir_tx(Cli* cli, FuriString* args) {
  141. UNUSED(cli);
  142. const char* str = furi_string_get_cstr(args);
  143. InfraredSignal* signal = infrared_signal_alloc();
  144. bool success = infrared_cli_parse_message(str, signal) || infrared_cli_parse_raw(str, signal);
  145. if(success) {
  146. infrared_signal_transmit(signal);
  147. } else {
  148. printf("Wrong arguments.\r\n");
  149. infrared_cli_print_usage();
  150. }
  151. infrared_signal_free(signal);
  152. }
  153. static bool
  154. infrared_cli_save_signal(InfraredSignal* signal, FlipperFormat* file, const char* name) {
  155. bool ret = infrared_signal_save(signal, file, name);
  156. if(!ret) {
  157. printf("Failed to save signal: \"%s\"\r\n", name);
  158. }
  159. return ret;
  160. }
  161. static bool infrared_cli_decode_raw_signal(
  162. InfraredRawSignal* raw_signal,
  163. InfraredDecoderHandler* decoder,
  164. FlipperFormat* output_file,
  165. const char* signal_name) {
  166. InfraredSignal* signal = infrared_signal_alloc();
  167. bool ret = false, level = true, is_decoded = false;
  168. size_t i;
  169. for(i = 0; i < raw_signal->timings_size; ++i) {
  170. // TODO: Any infrared_check_decoder_ready() magic?
  171. const InfraredMessage* message = infrared_decode(decoder, level, raw_signal->timings[i]);
  172. if(message) {
  173. is_decoded = true;
  174. printf(
  175. "Protocol: %s address: 0x%lX command: 0x%lX %s\r\n",
  176. infrared_get_protocol_name(message->protocol),
  177. message->address,
  178. message->command,
  179. (message->repeat ? "R" : ""));
  180. if(output_file && !message->repeat) {
  181. infrared_signal_set_message(signal, message);
  182. if(!infrared_cli_save_signal(signal, output_file, signal_name)) break;
  183. }
  184. }
  185. level = !level;
  186. }
  187. if(i == raw_signal->timings_size) {
  188. if(!is_decoded && output_file) {
  189. infrared_signal_set_raw_signal(
  190. signal,
  191. raw_signal->timings,
  192. raw_signal->timings_size,
  193. raw_signal->frequency,
  194. raw_signal->duty_cycle);
  195. ret = infrared_cli_save_signal(signal, output_file, signal_name);
  196. } else {
  197. ret = true;
  198. }
  199. }
  200. infrared_reset_decoder(decoder);
  201. infrared_signal_free(signal);
  202. return ret;
  203. }
  204. static bool infrared_cli_decode_file(FlipperFormat* input_file, FlipperFormat* output_file) {
  205. bool ret = false;
  206. InfraredSignal* signal = infrared_signal_alloc();
  207. InfraredDecoderHandler* decoder = infrared_alloc_decoder();
  208. FuriString* tmp;
  209. tmp = furi_string_alloc();
  210. while(infrared_signal_read(signal, input_file, tmp)) {
  211. ret = false;
  212. if(!infrared_signal_is_valid(signal)) {
  213. printf("Invalid signal\r\n");
  214. break;
  215. }
  216. if(!infrared_signal_is_raw(signal)) {
  217. if(output_file &&
  218. !infrared_cli_save_signal(signal, output_file, furi_string_get_cstr(tmp))) {
  219. break;
  220. } else {
  221. printf("Skipping decoded signal\r\n");
  222. continue;
  223. }
  224. }
  225. InfraredRawSignal* raw_signal = infrared_signal_get_raw_signal(signal);
  226. printf(
  227. "Raw signal: %s, %u samples\r\n", furi_string_get_cstr(tmp), raw_signal->timings_size);
  228. if(!infrared_cli_decode_raw_signal(
  229. raw_signal, decoder, output_file, furi_string_get_cstr(tmp)))
  230. break;
  231. ret = true;
  232. }
  233. infrared_free_decoder(decoder);
  234. infrared_signal_free(signal);
  235. furi_string_free(tmp);
  236. return ret;
  237. }
  238. static void infrared_cli_process_decode(Cli* cli, FuriString* args) {
  239. UNUSED(cli);
  240. Storage* storage = furi_record_open(RECORD_STORAGE);
  241. FlipperFormat* input_file = flipper_format_buffered_file_alloc(storage);
  242. FlipperFormat* output_file = NULL;
  243. uint32_t version;
  244. FuriString *tmp, *header, *input_path, *output_path;
  245. tmp = furi_string_alloc();
  246. header = furi_string_alloc();
  247. input_path = furi_string_alloc();
  248. output_path = furi_string_alloc();
  249. do {
  250. if(!args_read_probably_quoted_string_and_trim(args, input_path)) {
  251. printf("Wrong arguments.\r\n");
  252. infrared_cli_print_usage();
  253. break;
  254. }
  255. args_read_probably_quoted_string_and_trim(args, output_path);
  256. if(!flipper_format_buffered_file_open_existing(
  257. input_file, furi_string_get_cstr(input_path))) {
  258. printf(
  259. "Failed to open file for reading: \"%s\"\r\n", furi_string_get_cstr(input_path));
  260. break;
  261. }
  262. if(!flipper_format_read_header(input_file, header, &version) ||
  263. (!furi_string_start_with_str(header, "IR")) || version != 1) {
  264. printf(
  265. "Invalid or corrupted input file: \"%s\"\r\n", furi_string_get_cstr(input_path));
  266. break;
  267. }
  268. if(!furi_string_empty(output_path)) {
  269. printf("Writing output to file: \"%s\"\r\n", furi_string_get_cstr(output_path));
  270. output_file = flipper_format_file_alloc(storage);
  271. }
  272. if(output_file &&
  273. !flipper_format_file_open_always(output_file, furi_string_get_cstr(output_path))) {
  274. printf(
  275. "Failed to open file for writing: \"%s\"\r\n", furi_string_get_cstr(output_path));
  276. break;
  277. }
  278. if(output_file && !flipper_format_write_header(output_file, header, version)) {
  279. printf(
  280. "Failed to write to the output file: \"%s\"\r\n",
  281. furi_string_get_cstr(output_path));
  282. break;
  283. }
  284. if(!infrared_cli_decode_file(input_file, output_file)) {
  285. break;
  286. }
  287. printf("File successfully decoded.\r\n");
  288. } while(false);
  289. furi_string_free(tmp);
  290. furi_string_free(header);
  291. furi_string_free(input_path);
  292. furi_string_free(output_path);
  293. flipper_format_free(input_file);
  294. if(output_file) flipper_format_free(output_file);
  295. furi_record_close(RECORD_STORAGE);
  296. }
  297. static void infrared_cli_process_universal(Cli* cli, FuriString* args) {
  298. enum RemoteTypes Remote;
  299. FuriString* command;
  300. FuriString* remote;
  301. FuriString* signal;
  302. command = furi_string_alloc();
  303. remote = furi_string_alloc();
  304. signal = furi_string_alloc();
  305. do {
  306. if(!args_read_string_and_trim(args, command)) {
  307. infrared_cli_print_usage();
  308. break;
  309. }
  310. if(furi_string_cmp_str(command, "list") == 0) {
  311. args_read_string_and_trim(args, remote);
  312. if(furi_string_cmp_str(remote, "tv") == 0) {
  313. Remote = TV;
  314. } else if(furi_string_cmp_str(remote, "ac") == 0) {
  315. Remote = AC;
  316. } else {
  317. printf("Invalid remote type.\r\n");
  318. break;
  319. }
  320. infrared_cli_list_remote_signals(Remote);
  321. break;
  322. }
  323. if(furi_string_cmp_str(command, "tv") == 0) {
  324. Remote = TV;
  325. } else if(furi_string_cmp_str(command, "ac") == 0) {
  326. Remote = AC;
  327. } else {
  328. printf("Invalid remote type.\r\n");
  329. break;
  330. }
  331. args_read_string_and_trim(args, signal);
  332. if(furi_string_empty(signal)) {
  333. printf("Must supply a valid signal for type of remote selected.\r\n");
  334. break;
  335. }
  336. infrared_cli_brute_force_signals(cli, Remote, signal);
  337. break;
  338. } while(false);
  339. furi_string_free(command);
  340. furi_string_free(remote);
  341. furi_string_free(signal);
  342. }
  343. static void infrared_cli_list_remote_signals(enum RemoteTypes remote_type) {
  344. Storage* storage = furi_record_open(RECORD_STORAGE);
  345. FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
  346. dict_signals_t signals_dict;
  347. string_t key;
  348. const char* remote_file = NULL;
  349. bool success = false;
  350. int max = 1;
  351. switch(remote_type) {
  352. case TV:
  353. remote_file = EXT_PATH("infrared/assets/tv.ir");
  354. break;
  355. case AC:
  356. remote_file = EXT_PATH("infrared/assets/ac.ir");
  357. break;
  358. default:
  359. break;
  360. }
  361. dict_signals_init(signals_dict);
  362. string_init(key);
  363. success = flipper_format_buffered_file_open_existing(ff, remote_file);
  364. if(success) {
  365. FuriString* signal_name;
  366. signal_name = furi_string_alloc();
  367. printf("Valid signals:\r\n");
  368. while(flipper_format_read_string(ff, "name", signal_name)) {
  369. string_set_str(key, furi_string_get_cstr(signal_name));
  370. int* v = dict_signals_get(signals_dict, key);
  371. if(v != NULL) {
  372. (*v)++;
  373. max = M_MAX(*v, max);
  374. } else {
  375. dict_signals_set_at(signals_dict, key, 1);
  376. }
  377. }
  378. dict_signals_it_t it;
  379. for(dict_signals_it(it, signals_dict); !dict_signals_end_p(it); dict_signals_next(it)) {
  380. const struct dict_signals_pair_s* pair = dict_signals_cref(it);
  381. printf("\t%s\r\n", string_get_cstr(pair->key));
  382. }
  383. furi_string_free(signal_name);
  384. }
  385. string_clear(key);
  386. dict_signals_clear(signals_dict);
  387. flipper_format_free(ff);
  388. furi_record_close(RECORD_STORAGE);
  389. }
  390. static void
  391. infrared_cli_brute_force_signals(Cli* cli, enum RemoteTypes remote_type, FuriString* signal) {
  392. InfraredBruteForce* brute_force = infrared_brute_force_alloc();
  393. const char* remote_file = NULL;
  394. uint32_t i = 0;
  395. bool success = false;
  396. switch(remote_type) {
  397. case TV:
  398. remote_file = EXT_PATH("infrared/assets/tv.ir");
  399. break;
  400. case AC:
  401. remote_file = EXT_PATH("infrared/assets/ac.ir");
  402. break;
  403. default:
  404. break;
  405. }
  406. infrared_brute_force_set_db_filename(brute_force, remote_file);
  407. infrared_brute_force_add_record(brute_force, i++, furi_string_get_cstr(signal));
  408. success = infrared_brute_force_calculate_messages(brute_force);
  409. if(success) {
  410. uint32_t record_count;
  411. uint32_t index = 0;
  412. int records_sent = 0;
  413. bool running = false;
  414. running = infrared_brute_force_start(brute_force, index, &record_count);
  415. if(record_count <= 0) {
  416. printf("Invalid signal.\n");
  417. infrared_brute_force_reset(brute_force);
  418. return;
  419. }
  420. printf("Sending %ld codes to the tv.\r\n", record_count);
  421. printf("Press Ctrl-C to stop.\r\n");
  422. while(running) {
  423. running = infrared_brute_force_send_next(brute_force);
  424. if(cli_cmd_interrupt_received(cli)) break;
  425. printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
  426. fflush(stdout);
  427. }
  428. infrared_brute_force_stop(brute_force);
  429. } else {
  430. printf("Invalid signal.\r\n");
  431. }
  432. infrared_brute_force_reset(brute_force);
  433. infrared_brute_force_free(brute_force);
  434. }
  435. static void infrared_cli_start_ir(Cli* cli, FuriString* args, void* context) {
  436. UNUSED(context);
  437. if(furi_hal_infrared_is_busy()) {
  438. printf("INFRARED is busy. Exiting.");
  439. return;
  440. }
  441. FuriString* command;
  442. command = furi_string_alloc();
  443. args_read_string_and_trim(args, command);
  444. size_t i = 0;
  445. for(; i < COUNT_OF(infrared_cli_commands); ++i) {
  446. size_t cmd_len = strlen(infrared_cli_commands[i].cmd);
  447. if(!strncmp(furi_string_get_cstr(command), infrared_cli_commands[i].cmd, cmd_len)) {
  448. break;
  449. }
  450. }
  451. if(i < COUNT_OF(infrared_cli_commands)) {
  452. infrared_cli_commands[i].process_function(cli, args);
  453. } else {
  454. infrared_cli_print_usage();
  455. }
  456. furi_string_free(command);
  457. }
  458. void infrared_on_system_start() {
  459. #ifdef SRV_CLI
  460. Cli* cli = (Cli*)furi_record_open(RECORD_CLI);
  461. cli_add_command(cli, "ir", CliCommandFlagDefault, infrared_cli_start_ir, NULL);
  462. furi_record_close(RECORD_CLI);
  463. #else
  464. UNUSED(infrared_cli_start_ir);
  465. #endif
  466. }