mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
Added support to receive XMODEM-CRC files from the connected serial port.
This commit is contained in:
parent
94e40d82f3
commit
d10e762a7d
3 changed files with 403 additions and 7 deletions
20
src/tty.c
20
src/tty.c
|
|
@ -692,7 +692,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
{
|
{
|
||||||
tio_printf("Sending file '%s' ", line);
|
tio_printf("Sending file '%s' ", line);
|
||||||
tio_printf("Press any key to abort transfer");
|
tio_printf("Press any key to abort transfer");
|
||||||
tio_printf("%s", xymodem_send(device_fd, line, XMODEM_CRC) < 0 ? "Aborted" : "Done");
|
tio_printf("%s", xymodem_send(device_fd, line, XMODEM_1K) < 0 ? "Aborted" : "Done");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -707,6 +707,17 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case KEY_2:
|
||||||
|
tio_printf("Receive file with XMODEM-CRC");
|
||||||
|
tio_printf_raw("Enter file name: ");
|
||||||
|
if (tio_readln())
|
||||||
|
{
|
||||||
|
tio_printf("Ready to receiving file '%s' ", line);
|
||||||
|
tio_printf("Press any key to abort transfer");
|
||||||
|
tio_printf("%s", xymodem_receive(device_fd, line, XMODEM_CRC) < 0 ? "Aborted" : "Done");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
tio_error_print("Invalid protocol option");
|
tio_error_print("Invalid protocol option");
|
||||||
break;
|
break;
|
||||||
|
|
@ -758,7 +769,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key);
|
tio_printf(" ctrl-%c t Toggle line timestamp mode", option.prefix_key);
|
||||||
tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key);
|
tio_printf(" ctrl-%c U Toggle conversion to uppercase on output", option.prefix_key);
|
||||||
tio_printf(" ctrl-%c v Show version", option.prefix_key);
|
tio_printf(" ctrl-%c v Show version", option.prefix_key);
|
||||||
tio_printf(" ctrl-%c x Send file via Xmodem", option.prefix_key);
|
tio_printf(" ctrl-%c x Send/Receive file via Xmodem", option.prefix_key);
|
||||||
tio_printf(" ctrl-%c y Send file via Ymodem", option.prefix_key);
|
tio_printf(" ctrl-%c y Send file via Ymodem", option.prefix_key);
|
||||||
tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key);
|
tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key);
|
||||||
break;
|
break;
|
||||||
|
|
@ -956,8 +967,9 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
|
||||||
|
|
||||||
case KEY_X:
|
case KEY_X:
|
||||||
tio_printf("Please enter which X modem protocol to use:");
|
tio_printf("Please enter which X modem protocol to use:");
|
||||||
tio_printf(" (0) XMODEM-1K");
|
tio_printf(" (0) XMODEM-1K send");
|
||||||
tio_printf(" (1) XMODEM-CRC");
|
tio_printf(" (1) XMODEM-CRC send");
|
||||||
|
tio_printf(" (2) XMODEM-CRC receive");
|
||||||
// Process next input character as sub command
|
// Process next input character as sub command
|
||||||
sub_command = SUBCOMMAND_XMODEM;
|
sub_command = SUBCOMMAND_XMODEM;
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
388
src/xymodem.c
388
src/xymodem.c
|
|
@ -13,6 +13,7 @@
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <poll.h>
|
||||||
#include "xymodem.h"
|
#include "xymodem.h"
|
||||||
#include "print.h"
|
#include "print.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
|
@ -22,10 +23,20 @@
|
||||||
#define ACK 0x06
|
#define ACK 0x06
|
||||||
#define NAK 0x15
|
#define NAK 0x15
|
||||||
#define CAN 0x18
|
#define CAN 0x18
|
||||||
#define EOT "\004"
|
#define EOT 0x04
|
||||||
|
|
||||||
|
#define SOH_STR "\001"
|
||||||
|
#define ACK_STR "\006"
|
||||||
|
#define NAK_STR "\025"
|
||||||
|
#define CAN_STR "\030"
|
||||||
|
#define EOT_STR "\004"
|
||||||
|
|
||||||
#define OK 0
|
#define OK 0
|
||||||
#define ERR (-1)
|
#define ERR (-1)
|
||||||
|
#define ERR_FATAL (-2)
|
||||||
|
#define USER_CAN (-5)
|
||||||
|
|
||||||
|
#define RX_IGNORE 5
|
||||||
|
|
||||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
|
||||||
|
|
@ -161,7 +172,7 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq)
|
||||||
while (seq) {
|
while (seq) {
|
||||||
if (key_hit)
|
if (key_hit)
|
||||||
return ERR;
|
return ERR;
|
||||||
if (write(sio, EOT, 1) < 0) {
|
if (write(sio, EOT_STR, 1) < 0) {
|
||||||
tio_error_print("Write EOT to serial failed");
|
tio_error_print("Write EOT to serial failed");
|
||||||
return ERR;
|
return ERR;
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +291,7 @@ static int xmodem(int sio, const void *data, size_t len)
|
||||||
while (1) {
|
while (1) {
|
||||||
if (key_hit)
|
if (key_hit)
|
||||||
return ERR;
|
return ERR;
|
||||||
if (write(sio, EOT, 1) < 0) {
|
if (write(sio, EOT_STR, 1) < 0) {
|
||||||
tio_error_print("Write EOT to serial failed");
|
tio_error_print("Write EOT to serial failed");
|
||||||
return ERR;
|
return ERR;
|
||||||
}
|
}
|
||||||
|
|
@ -301,6 +312,345 @@ static int xmodem(int sio, const void *data, size_t len)
|
||||||
return 0; /* not reached */
|
return 0; /* not reached */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int start_receive(int sio)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
fds.fd = sio;
|
||||||
|
for (int n = 0; n < 20; n++)
|
||||||
|
{
|
||||||
|
/* Send the 'C' byte until the sender of the file responds with
|
||||||
|
something. The start character will be sent once a second for a number of
|
||||||
|
seconds. If nothing is received in that time then return false to indicate
|
||||||
|
that the transfer did not start. */
|
||||||
|
rc = write(sio, "C", 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
if (errno == EWOULDBLOCK) {
|
||||||
|
usleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tio_error_print("Write packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
/* Wait until data is available */
|
||||||
|
rc = poll(&fds, 1, 3000);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("%s", strerror(errno));
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
else if (rc > 0)
|
||||||
|
{
|
||||||
|
if (fds.revents & POLLIN)
|
||||||
|
{
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t update_CRC(uint16_t crc, char data_char)
|
||||||
|
{
|
||||||
|
uint8_t data = data_char;
|
||||||
|
crc = crc ^ ((uint16_t)data << 8);
|
||||||
|
for (int ix = 0; (ix < 8); ix++)
|
||||||
|
{
|
||||||
|
if (crc & 0x8000)
|
||||||
|
{
|
||||||
|
crc = (crc << 1) ^ 0x1021;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int receive_packet(int sio, struct xpacket packet, int fd)
|
||||||
|
{
|
||||||
|
char rxSeq1, rxSeq2 = 0;
|
||||||
|
char resp = 0;
|
||||||
|
uint16_t calcCrc = 0;
|
||||||
|
uint16_t rxCrc = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
fds.fd = sio;
|
||||||
|
|
||||||
|
/* Read seq bytes*/
|
||||||
|
rc = read_poll(sio, &rxSeq1, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for first seq byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading first seq byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
rc = read_poll(sio, &rxSeq2, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for second seq byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading second seq byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
/* Read packet Data */
|
||||||
|
for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++)
|
||||||
|
{
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
/* If the read times out or fails then fail this packet. */
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Timeout waiting for next packet char");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading next packet char")
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
packet.data[ix] = (uint8_t) resp;
|
||||||
|
calcCrc = update_CRC(calcCrc, resp);
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read CRC */
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for first CRC byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading first CRC byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t uresp = resp;
|
||||||
|
uint16_t uresp16 = uresp;
|
||||||
|
rxCrc = uresp16 << 8;
|
||||||
|
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for second CRC byte");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading second CRC byte")
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
uresp = resp;
|
||||||
|
uresp16 = uresp;
|
||||||
|
rxCrc |= uresp16;
|
||||||
|
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
/* At this point in the code, there should not be anything in the receive buffer
|
||||||
|
because the sender has just sent a complete packet and is waiting on a response. */
|
||||||
|
rc = poll(&fds, 1, 10);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("%s", strerror(errno));
|
||||||
|
tio_error_print("Poll check error after packet finish");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
else if (rc > 0)
|
||||||
|
{
|
||||||
|
if (fds.revents & POLLIN)
|
||||||
|
{
|
||||||
|
tio_error_print("RX sync error");
|
||||||
|
char dummy = 0;
|
||||||
|
/* Drain buffer */
|
||||||
|
while (read_poll(sio, &dummy, 1, 100) > 0) {}
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t tester = 0xff;
|
||||||
|
uint8_t seq1 = rxSeq1;
|
||||||
|
uint8_t seq2 = rxSeq2;
|
||||||
|
|
||||||
|
if ((calcCrc == rxCrc) && (seq1 == packet.seq - 1) && ((seq1 ^ seq2) == tester))
|
||||||
|
{
|
||||||
|
/* Resend of previously processed packet. */
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
return RX_IGNORE;
|
||||||
|
}
|
||||||
|
else if ((calcCrc != rxCrc) || (seq1 != packet.seq) || ((seq1 ^ seq2) != tester))
|
||||||
|
{
|
||||||
|
/* Fail if the CRC or sequence number is not correct or if the two received
|
||||||
|
sequence numbers are not the complement of one another. */
|
||||||
|
tio_error_print("Bad CRC or sequence number");
|
||||||
|
tio_debug_printf("CRC read: %u", rxCrc);
|
||||||
|
tio_debug_printf("CRC calculated: %u", calcCrc);
|
||||||
|
tio_debug_printf("Seq read: %hhu", rxSeq1);
|
||||||
|
tio_debug_printf("Seq should be: %hhu", packet.seq);
|
||||||
|
tio_debug_printf("inv seq: %hhu", rxSeq2);
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* The data is good. Process the packet then ACK it to the sender. */
|
||||||
|
rc = write(fd, packet.data, sizeof(packet.data));
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Problem writing to file");
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Write cancel packet to serial failed");
|
||||||
|
}
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR_FATAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xmodem_receive(int sio, int fd)
|
||||||
|
{
|
||||||
|
struct xpacket packet;
|
||||||
|
char resp = 0;
|
||||||
|
int rc;
|
||||||
|
bool complete = false;
|
||||||
|
char status;
|
||||||
|
|
||||||
|
/* Drain pending characters from serial line.*/
|
||||||
|
while(1) {
|
||||||
|
if (key_hit)
|
||||||
|
return -1;
|
||||||
|
rc = read_poll(sio, &resp, 1, 50);
|
||||||
|
if (rc == 0) {
|
||||||
|
if (resp == CAN) return ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (rc < 0) {
|
||||||
|
if (rc != USER_CAN) {
|
||||||
|
tio_error_print("Read sync from serial failed");
|
||||||
|
}
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Always work with 128b packets */
|
||||||
|
packet.seq = 1;
|
||||||
|
packet.type = SOH;
|
||||||
|
|
||||||
|
/* Start Receive*/
|
||||||
|
rc = start_receive(sio);
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Timeout waiting for transfer to start");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error starting XMODEM receive");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!complete) {
|
||||||
|
/* Poll for 1 new byte for 3 seconds */
|
||||||
|
rc = read_poll(sio, &resp, 1, 3000);
|
||||||
|
if (rc == 0) {
|
||||||
|
tio_error_print("Timeout waiting for start of next packet");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc < 0) {
|
||||||
|
tio_error_print("Error reading start of next packet")
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
if (key_hit)
|
||||||
|
return USER_CAN;
|
||||||
|
|
||||||
|
switch(resp)
|
||||||
|
{
|
||||||
|
case SOH:
|
||||||
|
/* Start of a packet */
|
||||||
|
rc = receive_packet(sio, packet, fd);
|
||||||
|
if (rc == OK) {
|
||||||
|
packet.seq++;
|
||||||
|
status = '.';
|
||||||
|
} else if (rc == ERR) {
|
||||||
|
rc = write(sio, NAK_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Writing not acknowledge packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
status = 'N';
|
||||||
|
} else if (rc == ERR_FATAL) {
|
||||||
|
tio_error_print("Receive cancelled due to fatal error");
|
||||||
|
return ERR;
|
||||||
|
} else if (rc == USER_CAN) {
|
||||||
|
rc = write(sio, CAN_STR, 1);
|
||||||
|
if (rc < 0) {
|
||||||
|
tio_error_print("Writing cancel to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
return USER_CAN;
|
||||||
|
} else if (rc == RX_IGNORE) {
|
||||||
|
status = ':';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EOT:
|
||||||
|
/* End of Transfer */
|
||||||
|
rc = write(sio, ACK_STR, 1);
|
||||||
|
if (rc < 0)
|
||||||
|
{
|
||||||
|
tio_error_print("Write acknowlegdement packet to serial failed");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
complete = true;
|
||||||
|
status = '\0';
|
||||||
|
write(STDOUT_FILENO, "|\r\n", 3);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CAN:
|
||||||
|
/* Cancel from sender */
|
||||||
|
tio_error_print("Transmission cancelled from sender");
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
tio_error_print("Unexpected character received waiting for next packet");
|
||||||
|
return ERR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Update "progress bar" */
|
||||||
|
write(STDOUT_FILENO, &status, 1);
|
||||||
|
}
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
||||||
{
|
{
|
||||||
size_t len;
|
size_t len;
|
||||||
|
|
@ -355,3 +705,35 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode)
|
||||||
close(fd);
|
close(fd);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int xymodem_receive(int sio, const char *filename, modem_mode_t mode)
|
||||||
|
{
|
||||||
|
int rc, fd;
|
||||||
|
|
||||||
|
/* Create new file */
|
||||||
|
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
if (fd < 0) {
|
||||||
|
tio_error_print("Could not open file");
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do transfer */
|
||||||
|
key_hit = 0;
|
||||||
|
if (mode == XMODEM_1K) {
|
||||||
|
tio_error_print("Not supported");
|
||||||
|
rc = -1;
|
||||||
|
}
|
||||||
|
else if (mode == XMODEM_CRC) {
|
||||||
|
rc = xmodem_receive(sio, fd);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tio_error_print("Not supported");
|
||||||
|
rc = -1;
|
||||||
|
}
|
||||||
|
key_hit = 0xff;
|
||||||
|
|
||||||
|
/* Flush serial and release resources */
|
||||||
|
tcflush(sio, TCIOFLUSH);
|
||||||
|
close(fd);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,5 @@ typedef enum {
|
||||||
extern char key_hit;
|
extern char key_hit;
|
||||||
|
|
||||||
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
|
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
|
||||||
|
|
||||||
|
int xymodem_receive(int sio, const char *filename, modem_mode_t mode);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue