| /* |
| * hdhomerun_config.c |
| * |
| * Copyright © 2006-2008 Silicondust USA Inc. <www.silicondust.com>. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 3 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * As a special exception to the GNU Lesser General Public License, |
| * you may link, statically or dynamically, an application with a |
| * publicly distributed version of the Library to produce an |
| * executable file containing portions of the Library, and |
| * distribute that executable file under terms of your choice, |
| * without any of the additional requirements listed in clause 4 of |
| * the GNU Lesser General Public License. |
| * |
| * By "a publicly distributed version of the Library", we mean |
| * either the unmodified Library as distributed by Silicondust, or a |
| * modified version of the Library that is distributed under the |
| * conditions defined in the GNU Lesser General Public License. |
| */ |
| |
| #include "hdhomerun.h" |
| |
| /* |
| * The console output format should be set to UTF-8, however in XP and Vista this breaks batch file processing. |
| * Attempting to restore on exit fails to restore if the program is terminated by the user. |
| * Solution - set the output format each printf. |
| */ |
| #if defined(__WINDOWS__) |
| #define printf console_printf |
| #define vprintf console_vprintf |
| #endif |
| |
| static const char *appname; |
| |
| struct hdhomerun_device_t *hd; |
| |
| static int help(void) |
| { |
| printf("Usage:\n"); |
| printf("\t%s discover\n", appname); |
| printf("\t%s <id> get help\n", appname); |
| printf("\t%s <id> get <item>\n", appname); |
| printf("\t%s <id> set <item> <value>\n", appname); |
| printf("\t%s <id> scan <tuner> [<filename>]\n", appname); |
| printf("\t%s <id> save <tuner> <filename>\n", appname); |
| printf("\t%s <id> upgrade <filename>\n", appname); |
| return -1; |
| } |
| |
| static void extract_appname(const char *argv0) |
| { |
| const char *ptr = strrchr(argv0, '/'); |
| if (ptr) { |
| argv0 = ptr + 1; |
| } |
| ptr = strrchr(argv0, '\\'); |
| if (ptr) { |
| argv0 = ptr + 1; |
| } |
| appname = argv0; |
| } |
| |
| static bool_t contains(const char *arg, const char *cmpstr) |
| { |
| if (strcmp(arg, cmpstr) == 0) { |
| return TRUE; |
| } |
| |
| if (*arg++ != '-') { |
| return FALSE; |
| } |
| if (*arg++ != '-') { |
| return FALSE; |
| } |
| if (strcmp(arg, cmpstr) == 0) { |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static uint32_t parse_ip_addr(const char *str) |
| { |
| unsigned int a[4]; |
| if (sscanf(str, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]) != 4) { |
| return 0; |
| } |
| |
| return (uint32_t)((a[0] << 24) | (a[1] << 16) | (a[2] << 8) | (a[3] << 0)); |
| } |
| |
| static int discover_print(char *target_ip_str) |
| { |
| uint32_t target_ip = 0; |
| if (target_ip_str) { |
| target_ip = parse_ip_addr(target_ip_str); |
| if (target_ip == 0) { |
| fprintf(stderr, "invalid ip address: %s\n", target_ip_str); |
| return -1; |
| } |
| } |
| |
| struct hdhomerun_discover_device_t result_list[64]; |
| int count = hdhomerun_discover_find_devices_custom(target_ip, HDHOMERUN_DEVICE_TYPE_TUNER, HDHOMERUN_DEVICE_ID_WILDCARD, result_list, 64); |
| if (count < 0) { |
| fprintf(stderr, "error sending discover request\n"); |
| return -1; |
| } |
| if (count == 0) { |
| printf("no devices found\n"); |
| return 0; |
| } |
| |
| int index; |
| for (index = 0; index < count; index++) { |
| struct hdhomerun_discover_device_t *result = &result_list[index]; |
| printf("hdhomerun device %08X found at %u.%u.%u.%u\n", |
| (unsigned int)result->device_id, |
| (unsigned int)(result->ip_addr >> 24) & 0x0FF, (unsigned int)(result->ip_addr >> 16) & 0x0FF, |
| (unsigned int)(result->ip_addr >> 8) & 0x0FF, (unsigned int)(result->ip_addr >> 0) & 0x0FF |
| ); |
| } |
| |
| return count; |
| } |
| |
| static int cmd_get(const char *item) |
| { |
| char *ret_value; |
| char *ret_error; |
| if (hdhomerun_device_get_var(hd, item, &ret_value, &ret_error) < 0) { |
| fprintf(stderr, "communication error sending request to hdhomerun device\n"); |
| return -1; |
| } |
| |
| if (ret_error) { |
| printf("%s\n", ret_error); |
| return 0; |
| } |
| |
| printf("%s\n", ret_value); |
| return 1; |
| } |
| |
| static int cmd_set_internal(const char *item, const char *value) |
| { |
| char *ret_error; |
| if (hdhomerun_device_set_var(hd, item, value, NULL, &ret_error) < 0) { |
| fprintf(stderr, "communication error sending request to hdhomerun device\n"); |
| return -1; |
| } |
| |
| if (ret_error) { |
| printf("%s\n", ret_error); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int cmd_set(const char *item, const char *value) |
| { |
| if (strcmp(value, "-") == 0) { |
| char *buffer = NULL; |
| size_t pos = 0; |
| |
| while (1) { |
| buffer = (char *)realloc(buffer, pos + 1024); |
| if (!buffer) { |
| fprintf(stderr, "out of memory\n"); |
| return -1; |
| } |
| |
| size_t size = fread(buffer + pos, 1, 1024, stdin); |
| pos += size; |
| |
| if (size < 1024) { |
| break; |
| } |
| } |
| |
| buffer[pos] = 0; |
| |
| int ret = cmd_set_internal(item, buffer); |
| |
| free(buffer); |
| return ret; |
| } |
| |
| return cmd_set_internal(item, value); |
| } |
| |
| static volatile sig_atomic_t sigabort_flag = FALSE; |
| static volatile sig_atomic_t siginfo_flag = FALSE; |
| |
| static void sigabort_handler(int arg) |
| { |
| sigabort_flag = TRUE; |
| } |
| |
| static void siginfo_handler(int arg) |
| { |
| siginfo_flag = TRUE; |
| } |
| |
| static void register_signal_handlers(sig_t sigpipe_handler, sig_t sigint_handler, sig_t siginfo_handler) |
| { |
| #if defined(SIGPIPE) |
| signal(SIGPIPE, sigpipe_handler); |
| #endif |
| #if defined(SIGINT) |
| signal(SIGINT, sigint_handler); |
| #endif |
| #if defined(SIGINFO) |
| signal(SIGINFO, siginfo_handler); |
| #endif |
| } |
| |
| static void cmd_scan_printf(FILE *fp, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| |
| if (fp) { |
| va_list apc; |
| va_copy(apc, ap); |
| |
| vfprintf(fp, fmt, apc); |
| fflush(fp); |
| |
| va_end(apc); |
| } |
| |
| vprintf(fmt, ap); |
| fflush(stdout); |
| |
| va_end(ap); |
| } |
| |
| static int cmd_scan(const char *tuner_str, const char *filename) |
| { |
| if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) { |
| fprintf(stderr, "invalid tuner number\n"); |
| return -1; |
| } |
| |
| char *ret_error; |
| if (hdhomerun_device_tuner_lockkey_request(hd, &ret_error) <= 0) { |
| fprintf(stderr, "failed to lock tuner\n"); |
| if (ret_error) { |
| fprintf(stderr, "%s\n", ret_error); |
| } |
| return -1; |
| } |
| |
| hdhomerun_device_set_tuner_target(hd, "none"); |
| |
| char *channelmap; |
| if (hdhomerun_device_get_tuner_channelmap(hd, &channelmap) <= 0) { |
| fprintf(stderr, "failed to query channelmap from device\n"); |
| return -1; |
| } |
| |
| const char *channelmap_scan_group = hdhomerun_channelmap_get_channelmap_scan_group(channelmap); |
| if (!channelmap_scan_group) { |
| fprintf(stderr, "unknown channelmap '%s'\n", channelmap); |
| return -1; |
| } |
| |
| if (hdhomerun_device_channelscan_init(hd, channelmap_scan_group) <= 0) { |
| fprintf(stderr, "failed to initialize channel scan\n"); |
| return -1; |
| } |
| |
| FILE *fp = NULL; |
| if (filename) { |
| fp = fopen(filename, "w"); |
| if (!fp) { |
| fprintf(stderr, "unable to create file: %s\n", filename); |
| return -1; |
| } |
| } |
| |
| register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler); |
| |
| int ret = 0; |
| while (!sigabort_flag) { |
| struct hdhomerun_channelscan_result_t result; |
| ret = hdhomerun_device_channelscan_advance(hd, &result); |
| if (ret <= 0) { |
| break; |
| } |
| |
| cmd_scan_printf(fp, "SCANNING: %u (%s)\n", |
| (unsigned int)result.frequency, result.channel_str |
| ); |
| |
| ret = hdhomerun_device_channelscan_detect(hd, &result); |
| if (ret < 0) { |
| break; |
| } |
| if (ret == 0) { |
| continue; |
| } |
| |
| cmd_scan_printf(fp, "LOCK: %s (ss=%u snq=%u seq=%u)\n", |
| result.status.lock_str, result.status.signal_strength, |
| result.status.signal_to_noise_quality, result.status.symbol_error_quality |
| ); |
| |
| if (result.transport_stream_id_detected) { |
| cmd_scan_printf(fp, "TSID: 0x%04X\n", result.transport_stream_id); |
| } |
| |
| int i; |
| for (i = 0; i < result.program_count; i++) { |
| struct hdhomerun_channelscan_program_t *program = &result.programs[i]; |
| cmd_scan_printf(fp, "PROGRAM %s\n", program->program_str); |
| } |
| } |
| |
| hdhomerun_device_tuner_lockkey_release(hd); |
| |
| if (fp) { |
| fclose(fp); |
| } |
| if (ret < 0) { |
| fprintf(stderr, "communication error sending request to hdhomerun device\n"); |
| } |
| return ret; |
| } |
| |
| static void cmd_save_print_stats(void) |
| { |
| struct hdhomerun_video_stats_t stats; |
| hdhomerun_device_get_video_stats(hd, &stats); |
| |
| fprintf(stderr, "%u packets received, %u overflow errors, %u network errors, %u transport errors, %u sequence errors\n", |
| (unsigned int)stats.packet_count, |
| (unsigned int)stats.overflow_error_count, |
| (unsigned int)stats.network_error_count, |
| (unsigned int)stats.transport_error_count, |
| (unsigned int)stats.sequence_error_count |
| ); |
| } |
| |
| static int cmd_save(const char *tuner_str, const char *filename) |
| { |
| if (hdhomerun_device_set_tuner_from_str(hd, tuner_str) <= 0) { |
| fprintf(stderr, "invalid tuner number\n"); |
| return -1; |
| } |
| |
| FILE *fp; |
| if (strcmp(filename, "null") == 0) { |
| fp = NULL; |
| } else if (strcmp(filename, "-") == 0) { |
| fp = stdout; |
| } else { |
| fp = fopen(filename, "wb"); |
| if (!fp) { |
| fprintf(stderr, "unable to create file %s\n", filename); |
| return -1; |
| } |
| } |
| |
| int ret = hdhomerun_device_stream_start(hd); |
| if (ret <= 0) { |
| fprintf(stderr, "unable to start stream\n"); |
| if (fp && fp != stdout) { |
| fclose(fp); |
| } |
| return ret; |
| } |
| |
| register_signal_handlers(sigabort_handler, sigabort_handler, siginfo_handler); |
| |
| struct hdhomerun_video_stats_t stats_old, stats_cur; |
| hdhomerun_device_get_video_stats(hd, &stats_old); |
| |
| uint64_t next_progress = getcurrenttime() + 1000; |
| |
| while (!sigabort_flag) { |
| uint64_t loop_start_time = getcurrenttime(); |
| |
| if (siginfo_flag) { |
| fprintf(stderr, "\n"); |
| cmd_save_print_stats(); |
| siginfo_flag = FALSE; |
| } |
| |
| size_t actual_size; |
| uint8_t *ptr = hdhomerun_device_stream_recv(hd, VIDEO_DATA_BUFFER_SIZE_1S, &actual_size); |
| if (!ptr) { |
| msleep_approx(64); |
| continue; |
| } |
| |
| if (fp) { |
| if (fwrite(ptr, 1, actual_size, fp) != actual_size) { |
| fprintf(stderr, "error writing output\n"); |
| return -1; |
| } |
| } |
| |
| if (loop_start_time >= next_progress) { |
| next_progress += 1000; |
| if (loop_start_time >= next_progress) { |
| next_progress = loop_start_time + 1000; |
| } |
| |
| /* Windows - indicate activity to suppress auto sleep mode. */ |
| #if defined(__WINDOWS__) |
| SetThreadExecutionState(ES_SYSTEM_REQUIRED); |
| #endif |
| |
| /* Video stats. */ |
| hdhomerun_device_get_video_stats(hd, &stats_cur); |
| |
| if (stats_cur.overflow_error_count > stats_old.overflow_error_count) { |
| fprintf(stderr, "o"); |
| } else if (stats_cur.network_error_count > stats_old.network_error_count) { |
| fprintf(stderr, "n"); |
| } else if (stats_cur.transport_error_count > stats_old.transport_error_count) { |
| fprintf(stderr, "t"); |
| } else if (stats_cur.sequence_error_count > stats_old.sequence_error_count) { |
| fprintf(stderr, "s"); |
| } else { |
| fprintf(stderr, "."); |
| } |
| |
| stats_old = stats_cur; |
| fflush(stderr); |
| } |
| |
| int32_t delay = 64 - (int32_t)(getcurrenttime() - loop_start_time); |
| if (delay <= 0) { |
| continue; |
| } |
| |
| msleep_approx(delay); |
| } |
| |
| if (fp) { |
| fclose(fp); |
| } |
| |
| hdhomerun_device_stream_stop(hd); |
| |
| fprintf(stderr, "\n"); |
| fprintf(stderr, "-- Video statistics --\n"); |
| cmd_save_print_stats(); |
| |
| return 0; |
| } |
| |
| static int cmd_upgrade(const char *filename) |
| { |
| FILE *fp = fopen(filename, "rb"); |
| if (!fp) { |
| fprintf(stderr, "unable to open file %s\n", filename); |
| return -1; |
| } |
| |
| printf("uploading firmware...\n"); |
| if (hdhomerun_device_upgrade(hd, fp) <= 0) { |
| fprintf(stderr, "error sending upgrade file to hdhomerun device\n"); |
| fclose(fp); |
| return -1; |
| } |
| |
| fclose(fp); |
| msleep_minimum(2000); |
| |
| printf("upgrading firmware...\n"); |
| msleep_minimum(8000); |
| |
| printf("rebooting...\n"); |
| int count = 0; |
| char *version_str; |
| while (1) { |
| if (hdhomerun_device_get_version(hd, &version_str, NULL) >= 0) { |
| break; |
| } |
| |
| count++; |
| if (count > 30) { |
| fprintf(stderr, "error finding device after firmware upgrade\n"); |
| return -1; |
| } |
| |
| msleep_minimum(1000); |
| } |
| |
| printf("upgrade complete - now running firmware %s\n", version_str); |
| return 0; |
| } |
| |
| static int cmd_execute(void) |
| { |
| char *ret_value; |
| char *ret_error; |
| if (hdhomerun_device_get_var(hd, "/sys/boot", &ret_value, &ret_error) < 0) { |
| fprintf(stderr, "communication error sending request to hdhomerun device\n"); |
| return -1; |
| } |
| |
| if (ret_error) { |
| printf("%s\n", ret_error); |
| return 0; |
| } |
| |
| char *end = ret_value + strlen(ret_value); |
| char *pos = ret_value; |
| |
| while (1) { |
| if (pos >= end) { |
| break; |
| } |
| |
| char *eol_r = strchr(pos, '\r'); |
| if (!eol_r) { |
| eol_r = end; |
| } |
| |
| char *eol_n = strchr(pos, '\n'); |
| if (!eol_n) { |
| eol_n = end; |
| } |
| |
| char *eol = eol_r; |
| if (eol_n < eol) { |
| eol = eol_n; |
| } |
| |
| char *sep = strchr(pos, ' '); |
| if (!sep || sep > eol) { |
| pos = eol + 1; |
| continue; |
| } |
| |
| *sep = 0; |
| *eol = 0; |
| |
| char *item = pos; |
| char *value = sep + 1; |
| |
| printf("set %s \"%s\"\n", item, value); |
| |
| cmd_set_internal(item, value); |
| |
| pos = eol + 1; |
| } |
| |
| return 1; |
| } |
| |
| static int main_cmd(int argc, char *argv[]) |
| { |
| if (argc < 1) { |
| return help(); |
| } |
| |
| char *cmd = *argv++; argc--; |
| |
| if (contains(cmd, "key")) { |
| if (argc < 2) { |
| return help(); |
| } |
| uint32_t lockkey = strtoul(argv[0], NULL, 0); |
| hdhomerun_device_tuner_lockkey_use_value(hd, lockkey); |
| |
| cmd = argv[1]; |
| argv+=2; argc-=2; |
| } |
| |
| if (contains(cmd, "get")) { |
| if (argc < 1) { |
| return help(); |
| } |
| return cmd_get(argv[0]); |
| } |
| |
| if (contains(cmd, "set")) { |
| if (argc < 2) { |
| return help(); |
| } |
| return cmd_set(argv[0], argv[1]); |
| } |
| |
| if (contains(cmd, "scan")) { |
| if (argc < 1) { |
| return help(); |
| } |
| if (argc < 2) { |
| return cmd_scan(argv[0], NULL); |
| } else { |
| return cmd_scan(argv[0], argv[1]); |
| } |
| } |
| |
| if (contains(cmd, "save")) { |
| if (argc < 2) { |
| return help(); |
| } |
| return cmd_save(argv[0], argv[1]); |
| } |
| |
| if (contains(cmd, "upgrade")) { |
| if (argc < 1) { |
| return help(); |
| } |
| return cmd_upgrade(argv[0]); |
| } |
| |
| if (contains(cmd, "execute")) { |
| return cmd_execute(); |
| } |
| |
| return help(); |
| } |
| |
| static int main_internal(int argc, char *argv[]) |
| { |
| #if defined(__WINDOWS__) |
| /* Initialize network socket support. */ |
| WORD wVersionRequested = MAKEWORD(2, 0); |
| WSADATA wsaData; |
| WSAStartup(wVersionRequested, &wsaData); |
| #endif |
| |
| extract_appname(argv[0]); |
| argv++; |
| argc--; |
| |
| if (argc == 0) { |
| return help(); |
| } |
| |
| char *id_str = *argv++; argc--; |
| if (contains(id_str, "help")) { |
| return help(); |
| } |
| if (contains(id_str, "discover")) { |
| if (argc < 1) { |
| return discover_print(NULL); |
| } else { |
| return discover_print(argv[0]); |
| } |
| } |
| |
| /* Device object. */ |
| hd = hdhomerun_device_create_from_str(id_str, NULL); |
| if (!hd) { |
| fprintf(stderr, "invalid device id: %s\n", id_str); |
| return -1; |
| } |
| |
| /* Device ID check. */ |
| uint32_t device_id_requested = hdhomerun_device_get_device_id_requested(hd); |
| if (!hdhomerun_discover_validate_device_id(device_id_requested)) { |
| fprintf(stderr, "invalid device id: %08X\n", (unsigned int)device_id_requested); |
| } |
| |
| /* Connect to device and check model. */ |
| const char *model = hdhomerun_device_get_model_str(hd); |
| if (!model) { |
| fprintf(stderr, "unable to connect to device\n"); |
| hdhomerun_device_destroy(hd); |
| return -1; |
| } |
| |
| /* Command. */ |
| int ret = main_cmd(argc, argv); |
| |
| /* Cleanup. */ |
| hdhomerun_device_destroy(hd); |
| |
| /* Complete. */ |
| return ret; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ret = main_internal(argc, argv); |
| if (ret <= 0) { |
| return 1; |
| } |
| return 0; |
| } |