Fix XModem for SecureCRT telnet client

This commit is contained in:
Theo Arends 2025-04-24 15:48:19 +02:00
parent 0ad8696dfc
commit 115cefc557
4 changed files with 90 additions and 27 deletions

View File

@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file.
### Added ### Added
- Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents - Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents
- Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once - Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once
- WebUI status line for MQTT and TLS, added `FUNC_WEB_STATUS` event - WebUI status line for MQTT and TLS, added `FUNC_WEB_STATUS` event (#23326)
### Breaking Changed ### Breaking Changed

View File

@ -118,6 +118,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl https://ota.tasm
### Added ### Added
- Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents - Command `JsonPP 0..7` to enable (>0) JSON Pretty Print on user interfaces and set number of indents
- Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once - Command `JsonPP <command>|backlog <command>;...` to enable JSON PP only once
- WebUI status line for MQTT and TLS, added `FUNC_WEB_STATUS` event [#23326](https://github.com/arendst/Tasmota/issues/23326)
### Breaking Changed ### Breaking Changed

View File

@ -912,6 +912,7 @@ void CmndStatus(void)
char stemp[200]; char stemp[200];
char stemp2[TOPSZ]; char stemp2[TOPSZ];
// Status
if ((0 == payload) || (-99 == payload)) { if ((0 == payload) || (-99 == payload)) {
uint32_t maxfn = (TasmotaGlobal.devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!TasmotaGlobal.devices_present) ? 1 : TasmotaGlobal.devices_present; uint32_t maxfn = (TasmotaGlobal.devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!TasmotaGlobal.devices_present) ? 1 : TasmotaGlobal.devices_present;
#ifdef USE_SONOFF_IFAN #ifdef USE_SONOFF_IFAN
@ -1111,6 +1112,7 @@ void CmndStatus(void)
CmndStatusResponse(6); CmndStatusResponse(6);
} }
// Status 7 - StatusTIM
if ((0 == payload) || (7 == payload)) { if ((0 == payload) || (7 == payload)) {
if (99 == Settings->timezone) { if (99 == Settings->timezone) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d" ), Settings->timezone); snprintf_P(stemp, sizeof(stemp), PSTR("%d" ), Settings->timezone);
@ -1133,6 +1135,7 @@ void CmndStatus(void)
#if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION) #if defined(USE_ENERGY_SENSOR) && defined(USE_ENERGY_MARGIN_DETECTION)
if (TasmotaGlobal.energy_driver) { if (TasmotaGlobal.energy_driver) {
// Status 9 - StatusPTH
if ((0 == payload) || (9 == payload)) { if ((0 == payload) || (9 == payload)) {
EnergyMarginStatus(); EnergyMarginStatus();
CmndStatusResponse(9); CmndStatusResponse(9);
@ -1169,7 +1172,7 @@ void CmndStatus(void)
#endif // FIRMWARE_MINIMAL #endif // FIRMWARE_MINIMAL
#ifdef USE_SHUTTER #ifdef USE_SHUTTER
// Status 13 // Status 13 - StatusSHT
if ((0 == payload) || (13 == payload)) { if ((0 == payload) || (13 == payload)) {
if (ShutterStatus()) { CmndStatusResponse(13); } if (ShutterStatus()) { CmndStatusResponse(13); }
} }

View File

@ -12,8 +12,10 @@
* *
* Usage: * Usage:
* Use a tool able supporting XModem file transfer and to connect either using serial or telnet with Tasmota. * Use a tool able supporting XModem file transfer and to connect either using serial or telnet with Tasmota.
* TeraTerm * TeraTerm (When using telnet enable non-bare CR support with Tasmota command `Teraterm 1`)
* SyncTerm * SyncTerm
* SecureCRT (When using Telnet set all Telnet Advanced Options OFF EXCEPT 'Server requires bare CR`)
*
* To start XModem file transfer first execute Tasmota command XSend or XReceive, * To start XModem file transfer first execute Tasmota command XSend or XReceive,
* then start tool XModem receive or send. * then start tool XModem receive or send.
* *
@ -37,6 +39,8 @@
#define XDRV_120 120 #define XDRV_120 120
//#define XYZM_DEBUG
#define XYZM_SOH 0x01 // Start of 128 byte data #define XYZM_SOH 0x01 // Start of 128 byte data
#define XYZM_STX 0x02 // Start of 1024 byte data #define XYZM_STX 0x02 // Start of 1024 byte data
#define XYZM_EOT 0x04 #define XYZM_EOT 0x04
@ -51,7 +55,7 @@
// Number of seconds until giving up hope of receiving sync packets from host. // Number of seconds until giving up hope of receiving sync packets from host.
const uint8_t XYZMODEM_SYNC_TIMEOUT = 30; const uint8_t XYZMODEM_SYNC_TIMEOUT = 30;
const uint8_t XYZMODEM_RECV_TIMEOUT_SHORT = 1; // Protocol = 10 seconds const uint8_t XYZMODEM_RECV_TIMEOUT_SHORT = 1; // Protocol = 10 seconds
const uint8_t XYZMODEM_RECV_TIMEOUT_LONG = 20; // Protocol = 60 seconds const uint8_t XYZMODEM_RECV_TIMEOUT_LONG = 3; // Protocol = 60 seconds
// Number of times we try to send a packet to the host until we give up sending.. // Number of times we try to send a packet to the host until we give up sending..
const uint8_t XYZMODEM_MAX_RETRY = 4; // Protocol = 10 for total packets to be send. Here retry per packet const uint8_t XYZMODEM_MAX_RETRY = 4; // Protocol = 10 for total packets to be send. Here retry per packet
@ -353,23 +357,51 @@ bool XYZModemReadAvailable(uint32_t timeout) {
} }
int XYZModemReadByte(void) { int XYZModemReadByte(void) {
if (!XYZModemReadAvailable(XYZModem.receive_timeout)) { uint8_t telnet_buffer[3];
return -1; while (true) {
} if (!XYZModemReadAvailable(XYZModem.receive_timeout)) {
int in_char = XYZModemRead(); return -1;
if (in_char >= 0) {
if (TXMP_TELNET == XYZModem.protocol) {
if (0xFF == in_char) { // Fix XModem over Telnet escape
XYZModemRead();
}
if (XYZModem.teraterm && (0x0D == in_char)) { // Fix TeraTerm
XYZModemRead();
}
} }
int in_char = XYZModemRead();
#ifdef XYZM_DEBUG
Serial.printf("%02X",in_char);
#endif
if (in_char >= 0) {
if (TXMP_TELNET == XYZModem.protocol) {
if (0xFF == in_char) { // Telnet IAC - Fix XModem over Telnet escape
telnet_buffer[0] = in_char;
int in_char2 = XYZModemRead();
#ifdef XYZM_DEBUG
Serial.printf("[%02X]",in_char2);
#endif
if ((in_char2 >= 251) && (in_char2 <= 254)) { // Telnet IAC - WILL, DO, WONT, DONT
if (251 == in_char2) { // 251 = 0xFB = WILL
telnet_buffer[1] = 254; // 254 = 0xFE = DONT
}
else if (253 == in_char2) { // 253 = 0xFD = DO
telnet_buffer[1] = 252; // 252 = 0xFC = WONT
}
int in_char3 = XYZModemRead();
telnet_buffer[2] = in_char3;
#ifdef XYZM_DEBUG
Serial.printf("(%02X)",in_char3);
#endif
XYZModemWriteBuf(telnet_buffer, 3); // Send not supported telnet functions reponse(s)
continue;
}
}
if (XYZModem.teraterm && (0x0D == in_char)) { // Fix TeraTerm
int in_char2 = XYZModemRead();
#ifdef XYZM_DEBUG
Serial.printf("[%02X]",in_char2);
#endif
}
}
return in_char;
}
break;
} }
// AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Rcvd %d"), in_char); return -1;
return in_char;
} }
/*********************************************************************************************\ /*********************************************************************************************\
@ -499,7 +531,7 @@ void XYZModemSendNakOrC(void) {
XYZModemWrite(out_char); XYZModemWrite(out_char);
} }
void XYZModemSendNak(void) { bool XYZModemSendNak(void) {
// When the receiver wishes to <nak>, it should call a "PURGE" subroutine, to wait // When the receiver wishes to <nak>, it should call a "PURGE" subroutine, to wait
// for the line to clear. Recall the sender tosses any characters in its buffer // for the line to clear. Recall the sender tosses any characters in its buffer
// immediately upon completing sending a block, to ensure no glitches were mis-interpreted. // immediately upon completing sending a block, to ensure no glitches were mis-interpreted.
@ -516,13 +548,14 @@ void XYZModemSendNak(void) {
XYZModem.nak_count--; XYZModem.nak_count--;
if (0 == XYZModem.nak_count) { if (0 == XYZModem.nak_count) {
XYZModemCancel(); // Cancel xfer XYZModemCancel(); // Cancel xfer
return; return false;
} }
} }
XYZModemWrite(XYZM_NAK); XYZModemWrite(XYZM_NAK);
XYZModem.mode = XYZD_SOH; XYZModem.mode = XYZD_SOH;
return true;
} }
bool XYZModemCheckPacket(uint8_t *buffer) { bool XYZModemCheckPacket(uint8_t *buffer) {
@ -630,21 +663,43 @@ int XYZModemReceive(uint32_t packet_no) {
} }
case XYZD_BLK1: { case XYZD_BLK1: {
// AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: XYZD_BLK1")); // AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: XYZD_BLK1"));
/*
// Needed with SecureCRT unless Session Option - Telnet - Server requires bare CR is set
if (TXMP_TELNET == XYZModem.protocol) {
if (0x0D == in_char) {
int in_char2 = XYZModemRead();
#ifdef XYZM_DEBUG
Serial.printf("[%02X]",in_char2);
#endif
}
}
*/
xmodem_buffer[1] = in_char; xmodem_buffer[1] = in_char;
XYZModem.mode = XYZD_BLK2; XYZModem.mode = XYZD_BLK2;
break; break;
} }
case XYZD_BLK2: { case XYZD_BLK2: {
// AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: XYZD_BLK2 %02X, exor %02X"), in_char, (in_char ^ xmodem_buffer[1])); // AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: XYZD_BLK2 %02X, exor %02X"), in_char, (in_char ^ xmodem_buffer[1]));
/*
// Needed with SecureCRT unless Session Option - Telnet - Server requires bare CR is set
if (TXMP_TELNET == XYZModem.protocol) {
if (0x0D == in_char) {
int in_char2 = XYZModemRead();
#ifdef XYZM_DEBUG
Serial.printf("[%02X]",in_char2);
#endif
}
}
*/
xmodem_buffer[2] = in_char; xmodem_buffer[2] = in_char;
if (0xFF == (in_char ^ xmodem_buffer[1])) { if (0xFF == (in_char ^ xmodem_buffer[1])) {
xmodem_buffer_ptr = 3; xmodem_buffer_ptr = 3;
packet_size = 3 + XYZModem.packet_size + ((XYZModem.oldChecksum) ? 1 : 2); packet_size = 3 + XYZModem.packet_size + ((XYZModem.oldChecksum) ? 1 : 2);
XYZModem.mode = XYZD_DATA; XYZModem.mode = XYZD_DATA;
} else { } else {
XYZModemSendNak(); if (!XYZModemSendNak()) {
return XYZS_PACKET;
}
} }
break; break;
} }
@ -653,7 +708,6 @@ int XYZModemReceive(uint32_t packet_no) {
xmodem_buffer[xmodem_buffer_ptr++] = in_char; xmodem_buffer[xmodem_buffer_ptr++] = in_char;
if (xmodem_buffer_ptr >= packet_size) { if (xmodem_buffer_ptr >= packet_size) {
// XYZFile.byte_counter += XYZModem.packet_size;
XYZFile.byte_counter = packet_no * XYZModem.packet_size; XYZFile.byte_counter = packet_no * XYZModem.packet_size;
XYZModem.mode = XYZD_SOH; XYZModem.mode = XYZD_SOH;
packet_ready = true; packet_ready = true;
@ -806,6 +860,9 @@ bool XYZModemLoop(void) {
} }
int result = XYZModemReceive(XYZModem.packet_no); int result = XYZModemReceive(XYZModem.packet_no);
if (result) { if (result) {
#ifdef XYZM_DEBUG
Serial.println();
#endif
switch (result) { switch (result) {
case XYZS_EOT: { case XYZS_EOT: {
XYZModemFileWriteEot(1); XYZModemFileWriteEot(1);
@ -824,6 +881,7 @@ bool XYZModemLoop(void) {
// Receive character timeout - will retry // Receive character timeout - will retry
AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Timeout - retry")); AddLog(LOG_LEVEL_DEBUG, PSTR("XMD: Timeout - retry"));
XYZModem.retry--; XYZModem.retry--;
XYZFile.step = XYZM_RECEIVE;
break; break;
} }
case XYZS_OTHER: { case XYZS_OTHER: {
@ -863,6 +921,9 @@ bool XYZModemLoop(void) {
return true; return true;
} }
} else { } else {
#ifdef XYZM_DEBUG
Serial.println();
#endif
XYZModem.packet_no++; XYZModem.packet_no++;
} }
break; break;
@ -916,7 +977,6 @@ void CmndXSend(void) {
if (!strcasecmp_P(XdrvMailbox.data, PSTR("Settings"))) { if (!strcasecmp_P(XdrvMailbox.data, PSTR("Settings"))) {
XYZFile.size = sizeof(TSettings); XYZFile.size = sizeof(TSettings);
XYZFile.file = false; XYZFile.file = false;
// XYZModem.enabled = XYZM_SEND;
ResponseCmndChar("Ready to start receive Settings"); ResponseCmndChar("Ready to start receive Settings");
#ifdef USE_UFILESYS #ifdef USE_UFILESYS
} else { } else {
@ -925,7 +985,6 @@ void CmndXSend(void) {
XYZFile.size = TfsFileSize(XYZFile.file_name); XYZFile.size = TfsFileSize(XYZFile.file_name);
if (XYZFile.size) { if (XYZFile.size) {
XYZFile.file = true; XYZFile.file = true;
// XYZModem.enabled = XYZM_SEND;
ResponseCmndChar("Ready to start receive file"); ResponseCmndChar("Ready to start receive file");
} else { } else {
ResponseCmndChar("File is empty"); ResponseCmndChar("File is empty");