Move elksmart-comm to another repository
This commit is contained in:
		
							parent
							
								
									bb4fdcd936
								
							
						
					
					
						commit
						8096a1b2c9
					
				| @ -19,14 +19,12 @@ find_package (PkgConfig REQUIRED) | ||||
| pkg_check_modules (x REQUIRED x11 xext xextproto) | ||||
| pkg_check_modules (pulse REQUIRED libpulse) | ||||
| pkg_check_modules (dbus REQUIRED dbus-1) | ||||
| pkg_check_modules (libusb libusb-1.0) | ||||
| pkg_check_modules (gdm gdm glib-2.0 gio-2.0) | ||||
| include_directories ( | ||||
| 	${x_INCLUDE_DIRS} ${pulse_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS}) | ||||
| link_directories ( | ||||
| 	${x_LIBRARY_DIRS} ${pulse_LIBRARY_DIRS} ${dbus_LIBRARY_DIRS}) | ||||
| 
 | ||||
| option (WITH_USB "Compile with USB utilities" ${libusb_FOUND}) | ||||
| option (WITH_GDM "Compile with GDM utilities" ${gdm_FOUND}) | ||||
| 
 | ||||
| # Generate a configuration file | ||||
| @ -59,13 +57,6 @@ target_link_libraries (wmstatus | ||||
| 	${x_LIBRARIES} ${pulse_LIBRARIES} ${dbus_LIBRARIES}) | ||||
| add_threads (wmstatus) | ||||
| 
 | ||||
| if (WITH_USB) | ||||
| 	list (APPEND targets elksmart-comm) | ||||
| 	add_executable (elksmart-comm elksmart-comm.c) | ||||
| 	target_include_directories (elksmart-comm PUBLIC ${libusb_INCLUDE_DIRS}) | ||||
| 	target_link_directories (elksmart-comm PUBLIC ${libusb_LIBRARY_DIRS}) | ||||
| 	target_link_libraries (elksmart-comm ${libusb_LIBRARIES}) | ||||
| endif () | ||||
| if (WITH_GDM) | ||||
| 	list (APPEND targets gdm-switch-user) | ||||
| 	add_executable (gdm-switch-user gdm-switch-user.c) | ||||
| @ -105,7 +96,7 @@ endif () | ||||
| 
 | ||||
| # These should be accessible by users, but need to touch system devices. | ||||
| # Use the setuid bit, for simplicity. | ||||
| foreach (target brightness input-switch elksmart-comm) | ||||
| foreach (target brightness input-switch) | ||||
| 	if (${target} IN_LIST targets) | ||||
| 		list (REMOVE_ITEM targets ${target}) | ||||
| 		install (TARGETS ${target} DESTINATION ${CMAKE_INSTALL_BINDIR} | ||||
|  | ||||
| @ -27,8 +27,6 @@ to other people as well: | ||||
|    pseudo-random data; it should be fast enough to saturate most devices | ||||
|  - 'big-brother' tracks the title of the active window and the idle state of | ||||
|    the user and writes these events to standard output. | ||||
|  - 'elksmart-comm' interfaces with ELK Smart infrared dongles, | ||||
|    partially reimplementing the Ocrustar mobile app. | ||||
| 
 | ||||
| Few of them are useful outside of Linux. | ||||
| 
 | ||||
| @ -40,8 +38,7 @@ Building | ||||
| -------- | ||||
| Build dependencies: CMake, pkg-config, liberty (included) + | ||||
| Runtime dependencies: libpulse, libx11, dbus-1 + | ||||
| Optional runtime dependencies: | ||||
| libusb-1.0 (elksmart-comm), libgdm (gdm-switch-user) | ||||
| Optional runtime dependencies: libgdm (gdm-switch-user) | ||||
| 
 | ||||
|  $ git clone --recursive https://git.janouch.name/p/desktop-tools.git | ||||
|  $ mkdir desktop-tools/build | ||||
|  | ||||
							
								
								
									
										703
									
								
								elksmart-comm.c
									
									
									
									
									
								
							
							
						
						
									
										703
									
								
								elksmart-comm.c
									
									
									
									
									
								
							| @ -1,703 +0,0 @@ | ||||
| /*
 | ||||
|  * elksmart-comm.c: ELK Smart infrared dongle tool (for EKX4S and EKX5S-T) | ||||
|  * | ||||
|  * Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name> | ||||
|  * | ||||
|  * Permission to use, copy, modify, and/or distribute this software for any | ||||
|  * purpose with or without fee is hereby granted. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
|  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
|  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | ||||
|  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
|  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | ||||
|  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
|  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #include "config.h" | ||||
| #undef PROGRAM_NAME | ||||
| #define PROGRAM_NAME "elksmart-comm" | ||||
| #include "liberty/liberty.c" | ||||
| 
 | ||||
| #include <libusb.h> | ||||
| 
 | ||||
| // --- Utilities ---------------------------------------------------------------
 | ||||
| 
 | ||||
| /// Search for a device with given vendor and product ID.
 | ||||
| /// This is quite similar to libusb_open_device_with_vid_pid().
 | ||||
| static libusb_device_handle * | ||||
| find_device (int vendor, int product, int *error) | ||||
| { | ||||
| 	libusb_device **list = NULL; | ||||
| 	libusb_device_handle *handle = NULL; | ||||
| 	int result = 0; | ||||
| 
 | ||||
| 	ssize_t len = libusb_get_device_list (NULL, &list); | ||||
| 	if (len < 0) | ||||
| 	{ | ||||
| 		result = len; | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	for (ssize_t i = 0; i < len; i++) | ||||
| 	{ | ||||
| 		libusb_device *device = list[i]; | ||||
| 		struct libusb_device_descriptor desc = {}; | ||||
| 		if ((result = libusb_get_device_descriptor (device, &desc))) | ||||
| 			print_debug ("%s", libusb_strerror (result)); | ||||
| 		else if (desc.idVendor != vendor || desc.idProduct != product) | ||||
| 			continue; | ||||
| 		else if (!(result = libusb_open (device, &handle))) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	libusb_free_device_list (list, true); | ||||
| out: | ||||
| 	if (error != NULL && result != 0) | ||||
| 		*error = result; | ||||
| 	return handle; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| wait_ms (long ms) | ||||
| { | ||||
| 	struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; | ||||
| 	nanosleep (&ts, NULL); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| dump_hex (const unsigned char *buf, size_t len) | ||||
| { | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 		printf ("%02x", buf[i]); | ||||
| 	printf ("\n"); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| read_hex (const char *string, struct str *out) | ||||
| { | ||||
| 	static const char *alphabet = "0123456789abcdef"; | ||||
| 	str_reset (out); | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		while (*string && strchr (" \t\n\r\v\f", *string)) | ||||
| 			string++; | ||||
| 		if (!*string) | ||||
| 			return true; | ||||
| 
 | ||||
| 		const char *hi, *lo; | ||||
| 		if (!(hi = strchr (alphabet, tolower_ascii (*string++))) || !*string | ||||
| 		 || !(lo = strchr (alphabet, tolower_ascii (*string++)))) | ||||
| 			return false; | ||||
| 
 | ||||
| 		str_pack_u8 (out, (hi - alphabet) << 4 | (lo - alphabet)); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // --- Coding ------------------------------------------------------------------
 | ||||
| 
 | ||||
| // Values are in microseconds.
 | ||||
| struct pulse { unsigned on, off; }; | ||||
| 
 | ||||
| static bool | ||||
| pulse_equal (struct pulse a, struct pulse b) | ||||
| { | ||||
| 	return a.on == b.on && a.off == b.off; | ||||
| } | ||||
| 
 | ||||
| static size_t | ||||
| decode_learned_direct (const uint8_t *b, size_t b_len, struct pulse *pulses) | ||||
| { | ||||
| 	size_t pulses_len = 0; | ||||
| 	for (size_t i = 0; i < b_len; ) | ||||
| 	{ | ||||
| 		struct pulse *pulse = &pulses[pulses_len++]; | ||||
| 		while (b[i] == 0xff) | ||||
| 		{ | ||||
| 			pulse->on += 4080; | ||||
| 			if (++i == b_len) | ||||
| 				return 0; | ||||
| 		} | ||||
| 		pulse->on += b[i++] * 16; | ||||
| 
 | ||||
| 		// Who cares, presumably it stays off.
 | ||||
| 		if (i == b_len) | ||||
| 			break; | ||||
| 
 | ||||
| 		while (b[i] == 0xff) | ||||
| 		{ | ||||
| 			pulse->off += 4080; | ||||
| 			if (++i == b_len) | ||||
| 				return 0; | ||||
| 		} | ||||
| 		pulse->off += b[i++] * 16; | ||||
| 	} | ||||
| 	return pulses_len; | ||||
| } | ||||
| 
 | ||||
| static struct pulse * | ||||
| decode_learned (const struct str *code, size_t *len, struct error **e) | ||||
| { | ||||
| 	// This conveniently has an upper bound.
 | ||||
| 	struct pulse *pulses = xcalloc (code->len, sizeof *pulses); | ||||
| 	if (!(*len = decode_learned_direct | ||||
| 		((const uint8_t *) code->str, code->len, pulses))) | ||||
| 	{ | ||||
| 		error_set (e, "code ends unexpectedly"); | ||||
| 		free (pulses); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return pulses; | ||||
| } | ||||
| 
 | ||||
| static struct pulse * | ||||
| encode_nec_byte (struct pulse *p, uint8_t byte) | ||||
| { | ||||
| 	for (int i = 7; i >= 0; i--) | ||||
| 		*p++ = (struct pulse) | ||||
| 			{ .on = 550, .off = ((byte >> i) & 1) ? 1650 : 550 }; | ||||
| 	return p; | ||||
| } | ||||
| 
 | ||||
| static struct pulse * | ||||
| encode_nec (const struct str *code, size_t *len, struct error **e) | ||||
| { | ||||
| 	if (code->len % 2) | ||||
| 	{ | ||||
| 		error_set (e, "NEC transmission format requires pairs"); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	// The timings seem to be rather tolerant.
 | ||||
| 	*len = code->len / 2 * (1 /* leader */ + 32 + 1 /* stop */); | ||||
| 	struct pulse *pulses = xcalloc (*len, sizeof *pulses), *p = pulses; | ||||
| 	for (size_t i = 0; i < code->len; i += 2) | ||||
| 	{ | ||||
| 		*p++ = (struct pulse) { .on = 8500, .off = 4250 }; | ||||
| 		p = encode_nec_byte (p, code->str[i + 0]); | ||||
| 		p = encode_nec_byte (p, ~code->str[i + 0]); | ||||
| 		p = encode_nec_byte (p, code->str[i + 1]); | ||||
| 		p = encode_nec_byte (p, ~code->str[i + 1]); | ||||
| 		*p++ = (struct pulse) { .on = 550, .off = 25000 }; | ||||
| 	} | ||||
| 	return pulses; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| compress_value (unsigned value, struct str *encoded) | ||||
| { | ||||
| 	if (value <= 2032) | ||||
| 	{ | ||||
| 		// We fix a minor problem in the original Ocrustar algorithm.
 | ||||
| 		uint8_t v = value / 16. + .5; | ||||
| 		str_pack_u8 (encoded, MAX (2, v)); | ||||
| 	} | ||||
| 	else | ||||
| 		do | ||||
| 		{ | ||||
| 			uint8_t v = value & 0x7f; | ||||
| 			if ((value >>= 7)) | ||||
| 				v |= 0x80; | ||||
| 			str_pack_u8 (encoded, v); | ||||
| 		} while (value); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| compress_pulses (const struct pulse *pulses, size_t len, struct str *encoded) | ||||
| { | ||||
| 	unsigned counts[len]; | ||||
| 	memset (counts, 0, sizeof counts); | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 		for (size_t k = 0; k < len; k++) | ||||
| 			if (pulse_equal (pulses[i], pulses[k])) | ||||
| 				counts[i]++; | ||||
| 
 | ||||
| 	struct pulse p1 = {}, p2 = {}; | ||||
| 	size_t top1 = 0, top2 = 0; | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 		if (counts[i] > counts[top1]) | ||||
| 			p1 = pulses[top1 = i]; | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 		if (counts[i] < counts[top1] | ||||
| 		 && counts[i] > counts[top2]) | ||||
| 			p2 = pulses[top2 = i]; | ||||
| 		else if (counts[top2] == counts[top1]) | ||||
| 			p2 = pulses[top2 = i]; | ||||
| 
 | ||||
| 	// Although I haven't really tried it, something tells me that
 | ||||
| 	// this will work even in the degenerated case of len <= 2.
 | ||||
| 	// XXX: The receiver might not like multibyte values here,
 | ||||
| 	//   Ocrustar also oddly replaces 0xff with 0xfe for these fields.
 | ||||
| 	compress_value (p2.on, encoded); | ||||
| 	compress_value (p2.off, encoded); | ||||
| 	compress_value (p1.on, encoded); | ||||
| 	compress_value (p1.off, encoded); | ||||
| 	str_pack_u8 (encoded, -1); | ||||
| 	str_pack_u8 (encoded, -1); | ||||
| 	str_pack_u8 (encoded, -1); | ||||
| 
 | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 	{ | ||||
| 		if (pulse_equal (pulses[i], p1)) | ||||
| 			str_pack_u8 (encoded, 0); | ||||
| 		else if (pulse_equal (pulses[i], p2)) | ||||
| 			str_pack_u8 (encoded, 1); | ||||
| 		else | ||||
| 		{ | ||||
| 			compress_value (pulses[i].on, encoded); | ||||
| 			compress_value (pulses[i].off, encoded); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // --- Device interaction ------------------------------------------------------
 | ||||
| 
 | ||||
| enum | ||||
| { | ||||
| 	USB_VENDOR_SMTCTL = 0x045c, | ||||
| 
 | ||||
| 	// 0x134 (EKX5S ~ 5s, 5th generation remote)
 | ||||
| 	// 0x195 (EKX4S ~ 4s, 4th generation remote)
 | ||||
| 	// 0x184 (EKX5S-T, international edition)
 | ||||
| 	USB_PRODUCT_SMTCTL_SMART_EKX4S = 0x0195, | ||||
| 	USB_PRODUCT_SMTCTL_SMART_EKX5S_T = 0x0184, | ||||
| 
 | ||||
| 	// There should only ever be one interface.
 | ||||
| 	USB_INTERFACE = 0, | ||||
| }; | ||||
| 
 | ||||
| static uint8_t | ||||
| 	c_transmit[] = { -1, -1, -1, -1 }, | ||||
| 	c_learn[]    = { -2, -2, -2, -2 }, | ||||
| 	c_stop[]     = { -3, -3, -3, -3 }, | ||||
| 	c_identify[] = { -4, -4, -4, -4 }; | ||||
| 
 | ||||
| static struct | ||||
| { | ||||
| 	unsigned char endpoint_out;         ///< Outgoing endpoint
 | ||||
| 	unsigned char endpoint_in;          ///< Incoming endpoint
 | ||||
| } | ||||
| g; | ||||
| 
 | ||||
| static bool | ||||
| init_device_from_desc (struct libusb_config_descriptor *desc, struct error **e) | ||||
| { | ||||
| 	// We're not being particuarly strict in here.
 | ||||
| 	if (desc->bNumInterfaces != 1) | ||||
| 		return error_set (e, "unexpected USB interface count"); | ||||
| 	if (desc->interface->num_altsetting != 1) | ||||
| 		return error_set (e, "unexpected alternate setting count"); | ||||
| 
 | ||||
| 	const struct libusb_interface_descriptor *asd = desc->interface->altsetting; | ||||
| 	if (asd->bInterfaceClass != LIBUSB_CLASS_COMM) | ||||
| 		return error_set (e, "unexpected USB interface class"); | ||||
| 	if (asd->bNumEndpoints != 2) | ||||
| 		return error_set (e, "unexpected endpoint count"); | ||||
| 
 | ||||
| 	bool have_out = false, have_in = false; | ||||
| 	for (uint8_t i = 0; i < asd->bNumEndpoints; i++) | ||||
| 	{ | ||||
| 		const struct libusb_endpoint_descriptor *epd = asd->endpoint + i; | ||||
| 		if ((epd->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) | ||||
| 			!= LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK) | ||||
| 			return error_set (e, "unexpected endpoint transfer type"); | ||||
| 
 | ||||
| 		switch ((epd->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)) | ||||
| 		{ | ||||
| 		break; case LIBUSB_ENDPOINT_OUT: | ||||
| 			have_out = true; | ||||
| 			g.endpoint_out = epd->bEndpointAddress; | ||||
| 		break; case LIBUSB_ENDPOINT_IN: | ||||
| 			have_in = true; | ||||
| 			g.endpoint_in = epd->bEndpointAddress; | ||||
| 		} | ||||
| 	} | ||||
| 	if (!have_out || !have_in) | ||||
| 		return error_set (e, "USB interface is not bidirectional"); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| init_device (libusb_device_handle *device, struct error **e) | ||||
| { | ||||
| 	struct libusb_config_descriptor *desc = NULL; | ||||
| 	int result = | ||||
| 		libusb_get_active_config_descriptor (libusb_get_device (device), &desc); | ||||
| 	if (result) | ||||
| 		return error_set (e, "%s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	bool ok = true; | ||||
| 	if ((result = libusb_kernel_driver_active (device, USB_INTERFACE)) == 1) | ||||
| 		ok = error_set (e, "device is claimed by a kernel driver"); | ||||
| 	else if (result) | ||||
| 		ok = error_set (e, "%s", libusb_strerror (result)); | ||||
| 	else | ||||
| 		ok = init_device_from_desc (desc, e); | ||||
| 
 | ||||
| 	libusb_free_config_descriptor (desc); | ||||
| 	return ok; | ||||
| } | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| static uint8_t | ||||
| mangle (uint8_t value) | ||||
| { | ||||
| 	uint8_t reversed = 0; | ||||
| 	for (int i = 0; i < 8; i++) | ||||
| 	{ | ||||
| 		reversed = (reversed << 1) | (value & 1); | ||||
| 		value >>= 1; | ||||
| 	} | ||||
| 	return ~reversed; | ||||
| } | ||||
| 
 | ||||
| static uint8_t | ||||
| checksum (const uint8_t *b, size_t len) | ||||
| { | ||||
| 	uint32_t sum = 0; | ||||
| 	for (size_t i = 0; i < len; i++) | ||||
| 		sum += b[i]; | ||||
| 	return mangle ((sum & 0xF0) | ((sum >> 8) & 0x0F)); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| send_transmit (libusb_device_handle *device, unsigned long frequency, | ||||
| 	const struct pulse *pulses, size_t pulses_len, struct error **e) | ||||
| { | ||||
| 	if (g_debug_mode) | ||||
| 		for (size_t i = 0; i < pulses_len; ) | ||||
| 		{ | ||||
| 			printf ("%u,%u", pulses[i].on, pulses[i].off); | ||||
| 			putchar (++i == pulses_len ? '\n' : ','); | ||||
| 		} | ||||
| 
 | ||||
| 	struct str compressed = str_make (); | ||||
| 	compress_pulses (pulses, pulses_len, &compressed); | ||||
| 
 | ||||
| 	struct str message = str_make (); | ||||
| 	str_append_data (&message, c_transmit, sizeof c_transmit); | ||||
| 	frequency += 0x7ffff; | ||||
| 	str_pack_u8 (&message, mangle (frequency >> 8)); | ||||
| 	str_pack_u8 (&message, mangle (frequency >> 16)); | ||||
| 	str_pack_u8 (&message, mangle (frequency)); | ||||
| 	str_pack_u8 (&message, mangle (compressed.len >> 8)); | ||||
| 	str_pack_u8 (&message, mangle (compressed.len)); | ||||
| 	str_append_str (&message, &compressed); | ||||
| 	str_free (&compressed); | ||||
| 
 | ||||
| 	size_t i = 0; | ||||
| 	uint8_t buffer[64]; | ||||
| 	bool ok = true; | ||||
| 	while (i != message.len) | ||||
| 	{ | ||||
| 		size_t chunk = MIN (62, message.len - i); | ||||
| 		memcpy (buffer, message.str + i, chunk); | ||||
| 		i += chunk; | ||||
| 		if (chunk == 62) | ||||
| 		{ | ||||
| 			buffer[chunk] = checksum (buffer, chunk); | ||||
| 			chunk++; | ||||
| 		} | ||||
| 
 | ||||
| 		int result = 0, len = 0; | ||||
| 		if ((result = libusb_bulk_transfer (device, g.endpoint_out, | ||||
| 			buffer, chunk, &len, 100))) | ||||
| 		{ | ||||
| 			ok = error_set (e, "send: %s", libusb_strerror (result)); | ||||
| 			break; | ||||
| 		} | ||||
| 		wait_ms (2); | ||||
| 	} | ||||
| 	str_free (&message); | ||||
| 	return ok; | ||||
| } | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| static bool | ||||
| pulse_is_likely_leader (const struct pulse *p) | ||||
| { | ||||
| 	return p->on >= 2048 && p->off >= 2048; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| try_to_depulse (const struct str *code) | ||||
| { | ||||
| 	size_t len = 0; | ||||
| 	struct pulse *pulses = decode_learned (code, &len, NULL); | ||||
| 	if (!pulses) | ||||
| 		return; | ||||
| 
 | ||||
| 	struct pulse *p = pulses, *end = p + len; | ||||
| 	while (p != end && pulse_is_likely_leader (p)) | ||||
| 	{ | ||||
| 		p++; | ||||
| 		printf ("Attempted pulse decode:\n"); | ||||
| 
 | ||||
| 		uint8_t bits = 0, nibble = 0; | ||||
| 		for (; p != end && !pulse_is_likely_leader (p); p++) | ||||
| 		{ | ||||
| 			nibble = nibble << 1 | (p->off > 2 * p->on); | ||||
| 			if (++bits == 4) | ||||
| 			{ | ||||
| 				putchar ("0123456789abcdef"[nibble]); | ||||
| 				bits = nibble = 0; | ||||
| 			} | ||||
| 		} | ||||
| 		putchar ('\n'); | ||||
| 	} | ||||
| 	free (pulses); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| recv_learn (libusb_device_handle *device, struct str *data, struct error **e) | ||||
| { | ||||
| 	uint8_t buffer[64] = {}; | ||||
| 	int result = 0, len = 0; | ||||
| 	while ((result = libusb_bulk_transfer (device, g.endpoint_in, | ||||
| 		buffer, sizeof buffer, &len, 100))) | ||||
| 	{ | ||||
| 		if (result != LIBUSB_ERROR_TIMEOUT) | ||||
| 			return error_set (e, "learn/recv: %s", libusb_strerror (result)); | ||||
| 		print_debug ("learn/recv: %s", libusb_strerror (result)); | ||||
| 	} | ||||
| 	if (len < 6 || memcmp (buffer, c_learn, sizeof c_learn)) | ||||
| 		return error_set (e, "learn/recv: %s", "unexpected response"); | ||||
| 
 | ||||
| 	// This field might only make sense for a later device,
 | ||||
| 	// because it doesn't always correspond with how much data we receive.
 | ||||
| 	// Nonetheless, it does match exactly often enough.
 | ||||
| 	size_t size = buffer[4] << 8 | buffer[5]; | ||||
| 	print_debug ("learn: code size: %zu", size); | ||||
| 
 | ||||
| 	str_append_data (data, buffer + 6, len - 6); | ||||
| 	dump_hex ((const unsigned char *) data->str, data->len); | ||||
| 	while (data->len < size) | ||||
| 	{ | ||||
| 		if (!(result = libusb_bulk_transfer (device, g.endpoint_in, | ||||
| 			buffer, sizeof buffer, &len, 100))) | ||||
| 		{ | ||||
| 			dump_hex (buffer, len); | ||||
| 
 | ||||
| 			str_append_data (data, buffer, len); | ||||
| 			print_debug ("learn: received %d (have %zu of %zu)", | ||||
| 				len, data->len, size); | ||||
| 			continue; | ||||
| 		} | ||||
| 		if (result != LIBUSB_ERROR_TIMEOUT) | ||||
| 			return error_set (e, "learn/recv: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 		// The device seems to queue up its output with pauses.
 | ||||
| 		print_debug ("learn/recv: %s", libusb_strerror (result)); | ||||
| 	} | ||||
| 
 | ||||
| 	// As far as I know, this doesn't do anything,
 | ||||
| 	// and the device doesn't accept it while scanning infrared codes either.
 | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_out, | ||||
| 		c_stop, sizeof c_stop, &len, 100))) | ||||
| 		return error_set (e, "learn/send: %s", libusb_strerror (result)); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| send_learn (libusb_device_handle *device, struct error **e) | ||||
| { | ||||
| 	int result = 0, len = 0; | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_out, | ||||
| 		c_learn, sizeof c_learn, &len, 100))) | ||||
| 		return error_set (e, "learn/send: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	printf ("Reading remote control codes.\n"); | ||||
| 	printf ("Press a remote control button from less than a centimeter.\n"); | ||||
| 	printf ("The dongle may be unusable until it returns some data.\n"); | ||||
| 	// ... Resetting the device using libusb_reset_device() doesn't help then.
 | ||||
| 	printf ("If the code fails to replay, retry the capture.\n"); | ||||
| 
 | ||||
| 	struct str data = str_make (); | ||||
| 	bool ok = recv_learn (device, &data, e); | ||||
| 	if (ok) | ||||
| 	{ | ||||
| 		printf ("Full command:\n"); | ||||
| 		dump_hex ((const unsigned char *) data.str, data.len); | ||||
| 		try_to_depulse (&data); | ||||
| 	} | ||||
| 
 | ||||
| 	str_free (&data); | ||||
| 	return ok; | ||||
| } | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| static bool | ||||
| send_identify (libusb_device_handle *device, struct error **e) | ||||
| { | ||||
| 	uint8_t buffer[64] = {}; | ||||
| 	int result = 0, len = 0; | ||||
| 	while (!(result = libusb_bulk_transfer (device, g.endpoint_in, | ||||
| 		buffer, sizeof buffer, &len, 10))) | ||||
| 		/* Flush buffers. */; | ||||
| 
 | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_out, | ||||
| 		c_identify, sizeof c_identify, &len, 100))) | ||||
| 		return error_set (e, "identify/send: %s", libusb_strerror (result)); | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_in, | ||||
| 		buffer, sizeof buffer, &len, 100))) | ||||
| 		return error_set (e, "identify/recv: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	// XXX: Sometimes, the device doesn't send any identification values.
 | ||||
| 	if (len != 6 || memcmp (buffer, c_identify, sizeof c_identify) | ||||
| 	 || buffer[4] != 0x70 || buffer[5] != 0x01) | ||||
| 		return error_set (e, "device busy or not supported"); | ||||
| 
 | ||||
| #if 0 | ||||
| 	// The EKX4S does not respond to this request.
 | ||||
| 	static uint8_t c_serial[] = { -5, -5, -5, -5 }; | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_out, | ||||
| 		c_serial, sizeof c_serial, &len, 100))) | ||||
| 		return error_set (e, "serial/send: %s", libusb_strerror (result)); | ||||
| 	if ((result = libusb_bulk_transfer (device, g.endpoint_in, | ||||
| 		buffer, sizeof buffer, &len, 100))) | ||||
| 		return error_set (e, "serial/recv: %s", libusb_strerror (result)); | ||||
| 	if (len < (int) sizeof c_serial | ||||
| 	 || memcmp (buffer, c_serial, sizeof c_serial)) | ||||
| 		return error_set (e, "serial retrieval failed"); | ||||
| #endif | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| run (libusb_device_handle *device, unsigned long frequency, bool nec, | ||||
| 	char **codes, size_t codes_len, struct error **e) | ||||
| { | ||||
| 	if (!send_identify (device, e)) | ||||
| 		return false; | ||||
| 	if (!codes_len) | ||||
| 		return send_learn (device, e); | ||||
| 
 | ||||
| 	struct str code = str_make (); | ||||
| 	bool ok = true; | ||||
| 	for (size_t i = 0; i < codes_len; i++) | ||||
| 	{ | ||||
| 		if (!read_hex (codes[i], &code)) | ||||
| 		{ | ||||
| 			ok = error_set (e, "invalid hex string"); | ||||
| 			break; | ||||
| 		} | ||||
| 
 | ||||
| 		size_t pulses_len = 0; | ||||
| 		struct pulse *pulses = nec | ||||
| 			? encode_nec (&code, &pulses_len, e) | ||||
| 			: decode_learned (&code, &pulses_len, e); | ||||
| 
 | ||||
| 		ok = pulses && send_transmit (device, frequency, pulses, pulses_len, e); | ||||
| 		free (pulses); | ||||
| 		if (!ok) | ||||
| 			break; | ||||
| 
 | ||||
| 		wait_ms (100); | ||||
| 	} | ||||
| 	str_free (&code); | ||||
| 	return ok; | ||||
| } | ||||
| 
 | ||||
| // --- Main --------------------------------------------------------------------
 | ||||
| 
 | ||||
| int | ||||
| main (int argc, char *argv[]) | ||||
| { | ||||
| 	unsigned long frequency = 38000; | ||||
| 	bool nec = false; | ||||
| 	static const struct opt opts[] = | ||||
| 	{ | ||||
| 		{ 'd', "debug", NULL, 0, "run in debug mode" }, | ||||
| 		{ 'f', "frequency", "HZ", 0, "frequency (38000 Hz by default)" }, | ||||
| 		{ 'n', "nec", NULL, 0, "use the NEC transmission format" }, | ||||
| 		{ 'h', "help", NULL, 0, "display this help and exit" }, | ||||
| 		{ 'V', "version", NULL, 0, "output version information and exit" }, | ||||
| 		{ 0, NULL, NULL, 0, NULL } | ||||
| 	}; | ||||
| 
 | ||||
| 	struct opt_handler oh = opt_handler_make (argc, argv, opts, "[COMMAND...]", | ||||
| 		"Transmit or receive infrared commands."); | ||||
| 
 | ||||
| 	int c; | ||||
| 	while ((c = opt_handler_get (&oh)) != -1) | ||||
| 	switch (c) | ||||
| 	{ | ||||
| 	case 'd': | ||||
| 		g_debug_mode = true; | ||||
| 		break; | ||||
| 	case 'f': | ||||
| 		if (!xstrtoul (&frequency, optarg, 10) || !frequency) | ||||
| 			exit_fatal ("invalid frequency"); | ||||
| 		break; | ||||
| 	case 'n': | ||||
| 		nec = true; | ||||
| 		break; | ||||
| 	case 'h': | ||||
| 		opt_handler_usage (&oh, stdout); | ||||
| 		exit (EXIT_SUCCESS); | ||||
| 	case 'V': | ||||
| 		printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); | ||||
| 		exit (EXIT_SUCCESS); | ||||
| 	default: | ||||
| 		print_error ("wrong options"); | ||||
| 		opt_handler_usage (&oh, stderr); | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| 
 | ||||
| 	argc -= optind; | ||||
| 	argv += optind; | ||||
| 
 | ||||
| 	opt_handler_free (&oh); | ||||
| 
 | ||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||
| 
 | ||||
| #if LIBUSB_API_VERSION >= 0x0100010A | ||||
| 	const struct libusb_init_option option = | ||||
| 	{ | ||||
| 		.option = LIBUSB_OPTION_LOG_LEVEL, | ||||
| 		.value.ival = LIBUSB_LOG_LEVEL_DEBUG, | ||||
| 	}; | ||||
| 	int result = libusb_init_context (NULL, &option, g_debug_mode); | ||||
| #else | ||||
| 	int result = libusb_init (NULL); | ||||
| #endif | ||||
| 	if (result) | ||||
| 		exit_fatal ("libusb: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	libusb_device_handle *device = NULL; | ||||
| 	if (!device && !result) | ||||
| 		device = find_device (USB_VENDOR_SMTCTL, | ||||
| 			USB_PRODUCT_SMTCTL_SMART_EKX4S, &result); | ||||
| 	if (!device && !result) | ||||
| 		device = find_device (USB_VENDOR_SMTCTL, | ||||
| 			USB_PRODUCT_SMTCTL_SMART_EKX5S_T, &result); | ||||
| 
 | ||||
| 	if (result) | ||||
| 		exit_fatal ("couldn't open device: %s", libusb_strerror (result)); | ||||
| 	else if (!device) | ||||
| 		exit_fatal ("no suitable device found"); | ||||
| 
 | ||||
| 	struct error *e = NULL; | ||||
| 	if (!init_device (device, &e)) | ||||
| 		exit_fatal ("%s", e->message); | ||||
| 	if ((result = libusb_claim_interface (device, USB_INTERFACE)) == 1) | ||||
| 		exit_fatal ("couldn't claim interface: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	if (!run (device, frequency, nec, argv, argc, &e)) | ||||
| 	{ | ||||
| 		print_error ("%s", e->message); | ||||
| 		error_free (e); | ||||
| 	} | ||||
| 
 | ||||
| 	if ((result = libusb_release_interface (device, USB_INTERFACE)) == 1) | ||||
| 		exit_fatal ("couldn't release interface: %s", libusb_strerror (result)); | ||||
| 
 | ||||
| 	libusb_close (device); | ||||
| 	libusb_exit (NULL); | ||||
| 	return 0; | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user