fxlink: add interactive mode to exchange data with gint

This commit is contained in:
Lephenixnoir 2021-05-25 20:46:27 +02:00
parent 30befdd2cf
commit d2b6da5122
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
8 changed files with 521 additions and 68 deletions

View File

@ -34,9 +34,10 @@ add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh")
# fxlink
configure_file(fxlink/config.h.in "${BIN}/include/fxlink/config.h")
add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/main.c
fxlink/properties.c fxlink/ud2.c fxlink/util.c)
target_link_libraries(fxlink PkgConfig::libusb) # PkgConfig::libudev
add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/interactive.c
fxlink/main.c fxlink/png.c fxlink/properties.c fxlink/ud2.c fxlink/util.c
fxlink/protocol.c)
target_link_libraries(fxlink PkgConfig::libpng PkgConfig::libusb) # PkgConfig::libudev
target_include_directories(fxlink PRIVATE "${BIN}/include/fxlink")
if(NOT FXLINK_DISABLE_UDISKS2)
target_link_libraries(fxlink PkgConfig::udisks2)

View File

@ -18,4 +18,7 @@ int main_blocks(filter_t *filter, delay_t *delay);
/* Main function for -s */
int main_send(filter_t *filter, delay_t *delay, char **files);
/* Main function for -i */
int main_interactive(filter_t *filter,delay_t *delay,libusb_context *context);
#endif /* FXLINK_FXLINK_H */

232
fxlink/interactive.c Normal file
View File

@ -0,0 +1,232 @@
#include "config.h"
#include "fxlink.h"
#include "util.h"
#include "properties.h"
#include "filter.h"
#include "protocol.h"
#include "usb.h"
#include "png.h"
#include <libusb.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <png.h>
static char *output_file(char const *path,char const *type,char const *suffix)
{
char *filename = NULL;
int counter = 1;
time_t time_raw;
struct tm time_bd;
time(&time_raw);
localtime_r(&time_raw, &time_bd);
while(1) {
asprintf(&filename, "%s/fxlink-%.16s-%04d.%02d.%02d-%02dh%02d-%d.%s",
path, type, time_bd.tm_year + 1900, time_bd.tm_mon,
time_bd.tm_mday, time_bd.tm_hour, time_bd.tm_min, counter, suffix);
if(!filename) continue;
/* Try to find a name for a file that doesn't exist */
if(access(filename, F_OK) == -1) break;
free(filename);
counter++;
}
return filename;
}
static bool message_new(message_t *msg, usb_fxlink_header_t const *h)
{
int version_major = (h->version >> 8) & 0xff;
int version_minor = (h->version) & 0xff;
fprintf(stderr, "New message (v%d.%d): application '%.16s', type '%.16s', "
"size %d bytes\n", version_major, version_minor, h->application,
h->type, h->size);
msg->output = malloc(h->size);
if(!msg->output) {
err("cannot allocate memory for message of %d bytes", h->size);
return false;
}
msg->header = *h;
msg->size_read = 0;
msg->valid = true;
return true;
}
static void message_finish(message_t *msg)
{
char const *path = ".";
if(!strncmp(msg->header.application, "fxlink", 16)) {
if(!strncmp(msg->header.type, "image", 16)) {
usb_fxlink_image_t *img = (void *)msg->output;
char *filename = output_file(path, msg->header.type, "png");
uint8_t **row_pointers = fxlink_protocol_decode_image(msg);
fxlink_png_save(row_pointers, img->width, img->height, filename);
printf("Saved image (%dx%d, format=%d) to '%s'\n",
img->width, img->height, img->pixel_format, filename);
free(row_pointers);
free(filename);
return;
}
if(!strncmp(msg->header.type, "text", 16)) {
printf("------------------\n");
fwrite(msg->output, 1, msg->header.size, stdout);
if(msg->output[msg->header.size - 1] != '\n') printf("\n");
printf("------------------\n");
return;
}
}
/* Default to saving to a blob */
char *filename = output_file(path, "blob", "bin");
FILE *fp = fopen(filename, "wb");
if(!fp) {
err("could not save to '%s': %m", filename);
return;
}
fwrite(msg->output, 1, msg->header.size, fp);
fclose(fp);
fprintf(stderr, "Saved as blob to '%s'\n", filename);
free(filename);
}
static void message_output(message_t *msg, void *buffer, int size)
{
int data_left = msg->header.size - msg->size_read;
printf("Got %d bytes of message data!\n", size);
if(size > data_left) {
err("Too much data in message, dropping %d bytes", size - data_left);
size = data_left;
}
memcpy(msg->output + msg->size_read, buffer, size);
msg->size_read += size;
if(msg->size_read >= msg->header.size) {
fprintf(stderr, "Successfully read %d bytes\n", msg->size_read);
message_finish(msg);
msg->valid = false;
}
}
int main_interactive(filter_t *filter, delay_t *delay, libusb_context *context)
{
libusb_device *dev = NULL;
libusb_device_handle *dh = NULL;
/* Wait for a device to be connected */
filter_clean_libusb(filter);
int rc = usb_unique_wait(filter, delay, context, &dev);
if(rc == FILTER_NONE) {
printf("No device found.\n");
return 1;
}
else if(rc == FILTER_MULTIPLE) {
printf("Multiple devices found, ambiguous!\n");
return 1;
}
if((rc = libusb_open(dev, &dh))) {
rc = libusb_err(rc, "cannot open device %s", usb_id(dev));
goto end;
}
/* Don't detach kernel drivers to avoid breaking the Mass Storage
communications if fxlink is ever started while the native LINK
application is running! */
libusb_set_auto_detach_kernel_driver(dh, false);
if((rc = libusb_claim_interface(dh, 0))) {
rc = libusb_err(rc, "cannot claim interface on %s", usb_id(dev));
goto end;
}
printf("Connected to %s, starting test.\n", usb_id(dev));
/* This buffer is used to receive messages; if the header is not complete
it is left in the buffer, hence the extra room */
__attribute__((aligned(4)))
static uint8_t buffer[2048 + sizeof(usb_fxlink_header_t)] = { 0 };
/* Amount of data in the buffer */
int buffer_size = 0;
/* Current message */
message_t msg = { 0 };
while(1)
{
int transferred = -1;
rc = libusb_bulk_transfer(dh, 0x81, buffer + buffer_size, 2048,
&transferred, 2000);
if(rc == LIBUSB_ERROR_NO_DEVICE) {
printf("Disconnected, leaving.\n");
break;
}
else if(rc && rc != LIBUSB_ERROR_TIMEOUT) {
rc = libusb_err(rc, "bulk transfer failed on %s", usb_id(dev));
continue;
}
if(transferred <= 0) continue;
buffer_size += transferred;
/* If there is an unfinished message, continue working on it */
if(msg.valid) {
message_output(&msg, buffer, buffer_size);
buffer_size = 0;
}
/* If the header is not yet fully transmitted, wait */
usb_fxlink_header_t *h = (void *)buffer;
if(buffer_size < (int)sizeof *h) continue;
/* Handle a new message */
if(h->version == 0x00000100) {
int data_size = buffer_size - sizeof *h;
if(!message_new(&msg, h))
printf("dropping %d bytes\n", data_size);
else
message_output(&msg, buffer + sizeof *h, data_size);
buffer_size = 0;
continue;
}
else {
err("invalid header, dropping %d bytes", transferred);
buffer_size = 0;
}
}
/* Save last unfinished message */
if(buffer_size > 0) {
printf("%d bytes not collected dropped\n", buffer_size);
}
rc = 0;
end:
if(dh) {
libusb_release_interface(dh, 0);
libusb_close(dh);
}
if(dev) libusb_unref_device(dev);
return rc;
}

View File

@ -12,8 +12,6 @@
#include <stdio.h>
#include <errno.h>
/* Main functions for each mdoe */
int main_list(filter_t *filter, delay_t *delay, libusb_context *context);
int main_test(libusb_device *device, libusb_context *context);
static const char *help_string =
@ -31,7 +29,7 @@ static const char *help_string =
" -l, --list List detected calculators on the USB ports (libusb)\n"
" -b, --blocks List detected Mass Storage filesystems (udisks2)\n"
" -s, --send Send a file to a Mass Storage calculator (udisks2)\n"
" --test Communication tests by Lephe (libusb) [WIP!]\n"
" -i, --interactive Interactive messaging with a gint add-in (libusb)\n"
"\n"
"General options:\n"
" -w DELAY Wait up to this many seconds for a calculator to\n"
@ -67,18 +65,18 @@ int main(int argc, char **argv)
// Command-line argument parsing
//---
enum { TEST=1, LIBUSB_LOG };
enum { LIBUSB_LOG=1 };
const struct option longs[] = {
{ "help", no_argument, NULL, 'h' },
{ "list", no_argument, NULL, 'l' },
{ "blocks", no_argument, NULL, 'b' },
{ "send", no_argument, NULL, 's' },
{ "test", no_argument, NULL, TEST },
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
{ "help", no_argument, NULL, 'h' },
{ "list", no_argument, NULL, 'l' },
{ "blocks", no_argument, NULL, 'b' },
{ "send", no_argument, NULL, 's' },
{ "interactive", no_argument, NULL, 'i' },
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
};
while(option >= 0 && option != '?')
switch((option = getopt_long(argc, argv, "hlbsf:w::", longs, NULL)))
switch((option = getopt_long(argc, argv, "hlbsif:w::", longs, NULL)))
{
case 'h':
fprintf(stderr, help_string, argv[0]);
@ -86,7 +84,7 @@ int main(int argc, char **argv)
case 'l':
case 'b':
case 's':
case TEST:
case 'i':
mode = option;
break;
case LIBUSB_LOG:
@ -141,7 +139,7 @@ int main(int argc, char **argv)
libusb_context *context = NULL;
/* Initialize libusb for corresponding modes */
if(mode == 'l' || mode == TEST) {
if(mode == 'l' || mode == 'i') {
if((rc = libusb_init(&context)))
return libusb_err(rc, "error initializing libusb");
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
@ -168,18 +166,8 @@ int main(int argc, char **argv)
rc = err("this fxlink was built without UDisks2; -s is disabled");
#endif
}
else if(mode == TEST) {
libusb_device *dev = NULL;
int rc = usb_unique_wait(filter, &delay, context, &dev);
if(rc == FILTER_NONE)
printf("No device found.\n");
else if(rc == FILTER_MULTIPLE)
printf("Multiple devices found, ambiguous!\n");
else if(rc == FILTER_UNIQUE) {
rc = main_test(dev, context);
libusb_unref_device(dev);
}
else if(mode == 'i') {
rc = main_interactive(filter, &delay, context);
}
if(context) libusb_exit(context);
@ -240,48 +228,9 @@ int main_list(filter_t *filter, delay_t *delay, libusb_context *context)
}
//---
// WIP tests
// libudev tests; work but not useful yet
//---
int main_test(libusb_device *dev, libusb_context *context)
{
libusb_device_handle *dh;
int rc;
(void)context;
if((rc = libusb_open(dev, &dh)))
return libusb_err(rc, "cannot open device %s", usb_id(dev));
/* When possible detach any existing driver */
libusb_set_auto_detach_kernel_driver(dh, true);
if((rc = libusb_claim_interface(dh, 0))) {
libusb_close(dh);
return libusb_err(rc, "cannot claim interface on %s", usb_id(dev));
}
uint8_t buffer[2048];
int transferred = -1;
while(1)
{
rc = libusb_bulk_transfer(dh, 0x81, buffer, 2048, &transferred, 500);
if((rc == 0 || rc == LIBUSB_ERROR_TIMEOUT) && transferred > 0)
fwrite(buffer, 1, transferred, stdout);
if(rc)
rc=libusb_err(rc,"cannot perform bulk transfer on %s",usb_id(dev));
fprintf(stderr, "Transferred: %d\n", transferred);
if(rc || transferred == 0) break;
}
libusb_release_interface(dh, 0);
libusb_close(dh);
return rc;
}
/* libudev tests, work but not useful yet */
#if 0
#include <libudev.h>
int main_udev_test(libusb_device *dev)

42
fxlink/png.c Normal file
View File

@ -0,0 +1,42 @@
#include "png.h"
#include "util.h"
#include <stdio.h>
/* fxlink_png_save(): Save a bitmap into a PNG file */
int fxlink_png_save(png_byte **row_pointers, int width, int height,
char const *path)
{
png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if(!png)
return err("failed to write PNG: png_create_write_struct");
png_infop info = png_create_info_struct(png);
if(!info)
return err("failed to write PNG: png_create_info_struct");
FILE *fp = fopen(path, "wb");
if(!fp) {
png_destroy_write_struct(&png, &info);
return err("failed to open '%s' to write PNG: %m", path);
}
if(setjmp(png_jmpbuf(png))) {
fclose(fp);
png_destroy_write_struct(&png, &info);
return 1;
}
png_init_io(png, fp);
png_set_IHDR(png, info,
width, height, 8,
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
png_write_image(png, row_pointers);
png_write_end(png, NULL);
png_destroy_write_struct(&png, &info);
fclose(fp);
return 0;
}

14
fxlink/png.h Normal file
View File

@ -0,0 +1,14 @@
//---
// fxlink:png - Tools to output PNG images with libpng
//---
#ifndef FXLINK_PNG_H
#define FXLINK_PNG_H
#include <png.h>
/* fxlink_png_save(): Save a bitmap into a PNG file */
int fxlink_png_save(png_byte **row_pointers, int width, int height,
char const *path);
#endif /* FXLINK_PNG_H */

141
fxlink/protocol.c Normal file
View File

@ -0,0 +1,141 @@
#include "protocol.h"
#include "util.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <endian.h>
//---
// Image format
//---
static int img_bytes_per_row(int format, int width)
{
if(format == USB_FXLINK_IMAGE_RGB565)
return 2 * width;
if(format == USB_FXLINK_IMAGE_MONO)
return (width + 7) >> 3;
if(format == USB_FXLINK_IMAGE_GRAY)
return 2 * ((width + 7) >> 3);
return 0;
}
static void decode_rgb565(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
{
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_RGB565, width);
for(int y = 0; y < height; y++) {
void *row = pixels + y * bpr;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + 2 * x;
uint16_t color = 0;
if(input - pixels + 2 <= size) color = *(uint16_t *)input;
color = be16toh(color);
row_pointers[y][3*x+0] = (color >> 11) << 3;
row_pointers[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
row_pointers[y][3*x+2] = (color & 0x1f) << 3;
}
}
}
static void decode_mono(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
{
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_MONO, width);
for(int y = 0; y < height; y++) {
void *row = pixels + y * bpr;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
row_pointers[y][3*x+0] = color;
row_pointers[y][3*x+1] = color;
row_pointers[y][3*x+2] = color;
}
}
}
static void decode_gray(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
{
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_MONO, width);
for(int k = 0; k < 2 * height; k++) {
void *row = pixels + k * bpr;
int y = k % height;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
int color = (byte & (0x80 >> (x & 7)));
/* Everything is inverted */
if(!color) color = (k >= height) ? 0xaa : 0x55;
else color = 0x00;
row_pointers[y][3*x+0] += color;
row_pointers[y][3*x+1] += color;
row_pointers[y][3*x+2] += color;
}
}
}
uint8_t **fxlink_protocol_decode_image(message_t *msg)
{
usb_fxlink_image_t *img = (void *)msg->output;
void *pixels = msg->output + sizeof *img;
/* Compute the amount of data for the specified image format and size */
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
int expected_size = img->height * bytes_per_row;
/* Check that the correct amount of data was sent */
int size = msg->size_read - sizeof *img;
if(size < expected_size)
printf("warning: got %d bytes but needed %d, image will be "
"incomplete\n", size, expected_size);
if(size > expected_size)
printf("warning: got %d bytes but needed %d for image, dropping extra"
"\n", size, expected_size);
/* Allocate row pointers */
uint8_t **row_pointers = malloc(img->height*sizeof *row_pointers);
if(!row_pointers) {
err("failed to write allocate memory to decode image");
return NULL;
}
for(size_t y = 0; y < img->height; y++) {
row_pointers[y] = calloc(img->width, 3);
if(!row_pointers[y]) {
err("failed to write allocate memory to decode image");
for(size_t i = 0 ; i < y; i++) free(row_pointers[i]);
free(row_pointers);
return NULL;
}
}
/* Decode image */
if(img->pixel_format == USB_FXLINK_IMAGE_RGB565)
decode_rgb565(pixels, img->width, img->height, size, row_pointers);
if(img->pixel_format == USB_FXLINK_IMAGE_MONO)
decode_mono(pixels, img->width, img->height, size, row_pointers);
if(img->pixel_format == USB_FXLINK_IMAGE_GRAY)
decode_gray(pixels, img->width, img->height, size, row_pointers);
return row_pointers;
}

71
fxlink/protocol.h Normal file
View File

@ -0,0 +1,71 @@
//---
// fxlink:protocol - Custom fxlink protocol
//---
#ifndef FXLINK_PROTOCOL_H
#define FXLINK_PROTOCOL_H
#include <stdint.h>
#include <stdbool.h>
/* See the gint source for details on the protocol */
typedef struct
{
uint32_t version;
uint32_t size;
uint32_t transfer_size;
char application[16];
char type[16];
} usb_fxlink_header_t;
/* Subheader for the fxlink built-in "image" type */
typedef struct
{
uint32_t width;
uint32_t height;
int pixel_format;
} usb_fxlink_image_t;
/* Pixel formats */
typedef enum
{
/* Image is an array of *big-endian* uint16_t with RGB565 format */
USB_FXLINK_IMAGE_RGB565 = 0,
/* Image is an array of bits in mono format */
USB_FXLINK_IMAGE_MONO,
/* Image is two consecutive mono arrays, one for light, one for dark */
USB_FXLINK_IMAGE_GRAY,
} usb_fxlink_image_format_t;
//---
// Utilities in this implementation
//---
/* Message currently being transferred */
typedef struct
{
usb_fxlink_header_t header;
/* Valid when we are reading a message */
bool valid;
/* Data already read in this message */
uint32_t size_read;
/* Data buffer */
char *output;
} message_t;
/* fxlink_protocol_decode_image(): Decode an image into RGB888 format
This function decodes the message into an RGB888 image and returns an array
of row pointers with the image data (free the array and each element of the
array after use).
If there are not enough bytes in the input, it pads with zeros, and if there
are extra bytes, they are dropped; in both cases a warning is printed. */
uint8_t **fxlink_protocol_decode_image(message_t *msg);
#endif /* FXLINK_PROTOCOL_H */