Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing features #3

Open
ammaree opened this issue Dec 30, 2024 · 10 comments
Open

Missing features #3

ammaree opened this issue Dec 30, 2024 · 10 comments

Comments

@ammaree
Copy link

ammaree commented Dec 30, 2024

Hi @igrr

Recently found this repo and was hoping it would be part for the solution for a burning need as identified in https://github.com/espressif/esp-dev-kits/issues/99

Whilst all the support for a basic RFC2217 session is there, the support required to enable remote UART flashing an ESP32 device is missing. Primarily the simulated control of DTR and RTS signals.

Any specific technical reason why this was omitted?

@igrr
Copy link
Owner

igrr commented Dec 30, 2024

Hi @ammaree, controlling DTR and RTS signals is already possible with the existing API, just not demonstrated in the example.

To support remote flashing, RFC2217 server needs to detect the beginning of the DTR/RTS reset sequence, and instead of passing it on to the GPIOs, it needs to generate a precise enough reset sequence itself — something similar to what this code in esp_rfc2217_server is doing. I think this doesn't require changes in rfc2217-server component and can be implemented in the on_control callback of the application.

If I find some time to work on rfc2217-server component I'll add an example to flash an ESP chip remotely.

@ammaree
Copy link
Author

ammaree commented Dec 30, 2024

Hi @igrr thanks for the feedback.

The most important item for me to move forward is the DTR/DSR support. I have started adding it (simplistically) by toggling DSR & DTR pins as required, but obviously that will not be sufficient.

Another item I need to add is simultaneous support for at least 2 telnet sessions:
a) port 23 connection feeding HOST device CLI (as if from local UART); and
b) port 1023 connection feeding TARGET device CLI and flashing

Any suggestions on how to adapt your RFC2217 component to handle 2 simultaneous sessions in the most effective manner?

@igrr
Copy link
Owner

igrr commented Dec 30, 2024

Could you please clarify what you mean by "HOST device CLI"? Does it mean simply forwarding the console logs of the rfc2217-server to the TCP port? Or is there an actual CLI with line editing, command prompt, etc?

@ammaree
Copy link
Author

ammaree commented Dec 30, 2024

We have an actual CLI on the Telnet RFC2217 HOST device, primarily used for Wifi configuration but also with significant debug capability.

What we would like to achieve with the first Telnet session is to remotely have access the CLI on the HOST device

The second Telnet session would be used to "passthrough" commands to the CLI on the TARGET device as well as to reflash the TARGET device using the RFC2217 functionality

@ammaree
Copy link
Author

ammaree commented Jan 4, 2025

Just to possibly clarify, functionality similar to the original ESP-Link on 8266, but without the Arduino/AVR, ARM support, no MQTT or outbound REST HTTP functionality.

The webserver as an alternative console maybe nice to have, but critical is ...
a: 1st Telnet session to access host CLI;
b: 2nd Telnet to access console output and flash the target via UART

Host device to be small and compact, powered from the target device. Ideal existing platform would be ESP32-S3-USB-BRIDGE, if only I can source them.

Ultimately the perfect Espressif based remote support device to support remotely deployed Espressif devices!

@ammaree
Copy link
Author

ammaree commented Jan 7, 2025

@igrr Apologies if you are on leave, but just another update from my side.

I have added support for the DTR and RTS pins and configured as per code below:

static esp_err_t init_uart(void) {
	uart_config_t uart_config = {
		.baud_rate = 115200,
		.data_bits = UART_DATA_8_BITS,
		.parity = UART_PARITY_DISABLE,
		.stop_bits = UART_STOP_BITS_1,
		.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
	};
	const size_t tx_buffer_size = 4096;
	const size_t rx_buffer_size = 4096;
	ESP_RETURN_ON_ERROR(uart_driver_install(CONFIG_EXAMPLE_UART_PORT_NUM, rx_buffer_size, tx_buffer_size, 0, NULL, ESP_INTR_FLAG_LOWMED), TAG, "uart_driver_install failed");
	ESP_RETURN_ON_ERROR(uart_param_config(CONFIG_EXAMPLE_UART_PORT_NUM, &uart_config), TAG, "uart_param_config failed");
	ESP_RETURN_ON_ERROR(uart_set_pin(CONFIG_EXAMPLE_UART_PORT_NUM, CONFIG_EXAMPLE_UART_TX_GPIO, CONFIG_EXAMPLE_UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "uart_set_pin failed");
	// configure simulates RTS & DSR pins and normal outputs
	gpio_config_t config_dtr = {
		.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_UART_DTR_GPIO),
		.mode = GPIO_MODE_OUTPUT_OD,
		.pull_up_en = GPIO_PULLUP_DISABLE, // ENABLE,
		.pull_down_en = GPIO_PULLDOWN_DISABLE,
		.intr_type = GPIO_INTR_DISABLE,
	};
	ESP_ERROR_CHECK(gpio_config((const gpio_config_t *) &config_dtr));
	gpio_config_t config_rts = {
		.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_UART_RTS_GPIO),
		.mode = GPIO_MODE_OUTPUT_OD,
		.pull_up_en = GPIO_PULLUP_DISABLE, // ENABLE,
		.pull_down_en = GPIO_PULLDOWN_DISABLE,
		.intr_type = GPIO_INTR_DISABLE,
	};
	ESP_ERROR_CHECK(gpio_config((const gpio_config_t *) &config_rts));
	return ESP_OK;
}

In addition I have add basic support for the minimal control options, see the code below:

static unsigned on_control(void * ctx, unsigned control) {
	static int64_t dtr_on, dtr_off, rts_on, rts_off;
	gpio_num_t pin;
	uint32_t level;
	if (control == RFC2217_CONTROL_SET_NO_FLOW_CONTROL) {		   // 1
		ESP_ERROR_CHECK(uart_set_hw_flow_ctrl(CONFIG_EXAMPLE_UART_PORT_NUM, UART_HW_FLOWCTRL_DISABLE, 0));
		ESP_ERROR_CHECK(uart_set_sw_flow_ctrl(CONFIG_EXAMPLE_UART_PORT_NUM, 0, 0, 0));
		goto exit;
	} else if (control == RFC2217_CONTROL_SET_DTR) {				// 8
		dtr_on = esp_timer_get_time();
		pin = CONFIG_EXAMPLE_UART_DTR_GPIO;
		level = 0;
//		ESP_LOGW(TAG, "C=%u  P=%u  DTR_%lu", control, pin, level);
	} else if (control == RFC2217_CONTROL_CLEAR_DTR) {			  // 9
		dtr_off = esp_timer_get_time();
		pin = CONFIG_EXAMPLE_UART_DTR_GPIO;
		level = 1;
//		ESP_LOGW(TAG, "C=%u  P=%u  DTR_%lu=%lld", control, pin, level, dtr_off - dtr_on);
	} else if (control == RFC2217_CONTROL_SET_RTS) {				// 11
		rts_on = esp_timer_get_time();
		pin = CONFIG_EXAMPLE_UART_RTS_GPIO;
		level = 0;
//		ESP_LOGW(TAG, "C=%u  P=%u  RTS_%lu", control, pin, level);
	} else if (control == RFC2217_CONTROL_CLEAR_RTS) {			  // 12
		rts_off = esp_timer_get_time();
		pin = CONFIG_EXAMPLE_UART_RTS_GPIO;
		level = 1;
//		ESP_LOGW(TAG, "C=%u  P=%u  RTS_%lu=%lld", control, pin, level, rts_off - rts_on);
	} else {
		ESP_LOGE(TAG, "Control %u not supported", control);
		return 0;
	}
	ESP_ERROR_CHECK(gpio_set_level(pin, level));
exit:
	return control;
}

I have the software running on a Lolin Mini-S3 and can connect to the target device using:
' idf.py -p rfc2217://w.x.y.z:port monitor'

The UART to Telnet functionality is working perfectly in both directions.
The reset functionality also works reliably.
Problem start with putting the target device into download mode, which does not work, comes up a a number of python errors.

I have done some research and it seems as if the esptool.py

	// The assignment of BOOT=1 and RST=1 is postponed and it is done only if no other state change occurs in time
	// period set by the timer.
	// This is a patch for Esptool. Esptool generates DTR=0 & RTS=1 followed by DTR=1 & RTS=0. However, a callback
	// with DTR = 1 & RTS = 1 is received between. This would prevent to put the target chip into download mode.

	// On ESP32, TDI jtag signal is on GPIO12, which is also a strapping pin that determines flash voltage.
	// If TDI is high when ESP32 is released from external reset, the flash voltage is set to 1.8V, and the chip will fail to boot.
	// As a solution, MTDI signal forced to be low when RST is about to go high.

I also had a look at the link to the Python server code but that made very little sense to me based on my limited Python skills

Where can I find some specific detail on the ESP32 reset sequence, specifically the start and end conditions as well as the exact timing required?

Alternatively, if you have made some progress with the flashing example that would be wonderful

André

@igrr
Copy link
Owner

igrr commented Jan 9, 2025

Thanks for explaining the use case. Regarding adding generic Telnet support, I think it is possible. However since I don't have a need for such functionality myself, I don't think I'll find time to add it to this project. If someone contributes that, I'll accept reasonable PRs, of course.

Where can I find some specific detail on the ESP32 reset sequence, specifically the start and end conditions as well as the exact timing required?

ESP32 datasheet contains the timing parameters, please search for Figure 3-1. Visualization of Timing Parameters for the Strapping Pins and Figure 2-4. Visualization of Timing Parameters for Power-up and Reset.

If you are driving the reset and GPIO0 pins of ESP32 via a transistor pair, as often done on development boards, you need to consider the delays introduced by this circuit. The exact timings will depend on the parameters of this circuit, you probably have to analyze this yourself.

@ammaree
Copy link
Author

ammaree commented Jan 14, 2025

Hi @igrr

Thanks for the feedback. As an update I have added the following functionality...

rfc2217-server/include/rfc2217_server.h:

Fixed RFC2217_PURGE_????? enum values should be 1/2/3
Added declarations for datasize, parity, stopsize and purge requests
Also added members to the server structure for the additional operations added

#pragma once

#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief RFC2217 server instance handle
 */
typedef struct rfc2217_server_s *rfc2217_server_t;

/**
 * @brief RFC2217 control signal definitions
 * FIXME: split this into separate enums and callbacks
 */
typedef enum {
    RFC2217_CONTROL_SET_NO_FLOW_CONTROL = 1,  //!< Set no flow control
    RFC2217_CONTROL_SET_XON_XOFF_FLOW_CONTROL = 2,  //!< Set XON/XOFF flow control
    RFC2217_CONTROL_SET_HARDWARE_FLOW_CONTROL = 3,  //!< Set hardware flow control
    RFC2217_CONTROL_SET_BREAK = 5,  //!< Set break signal
    RFC2217_CONTROL_CLEAR_BREAK = 6,    //!< Clear break signal
    RFC2217_CONTROL_SET_DTR = 8,    //!< Set DTR signal
    RFC2217_CONTROL_CLEAR_DTR = 9,  //!< Clear DTR signal
    RFC2217_CONTROL_SET_RTS = 11,   //!< Set RTS signal
    RFC2217_CONTROL_CLEAR_RTS = 12  //!< Clear RTS signal
} rfc2217_control_t;

/**
 * @brief RFC2217 purge request definitions
 */
typedef enum {
    RFC2217_PURGE_RECEIVE = 1,      //!< Request to purge receive buffer
    RFC2217_PURGE_TRANSMIT = 2,     //!< Request to purge transmit buffer
    RFC2217_PURGE_BOTH = 3          //!< Request to purge both receive and transmit buffers
} rfc2217_purge_t;

/**
 * @brief baudrate change request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_baudrate requested baudrate
 * @return actual baudrate that was set
 */
typedef unsigned (*rfc2217_on_baudrate_t)(void *ctx, unsigned requested_baudrate);

/**
 * @brief datasize change request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_baudrate requested datasize
 * @return actual datasize that was set
 */
typedef unsigned (*rfc2217_on_datasize_t)(void *ctx, unsigned requested_datasize);

/**
 * @brief parity change request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_baudrate requested parity
 * @return actual parity that was set
 */
typedef unsigned (*rfc2217_on_parity_t)(void *ctx, unsigned requested_parity);

/**
 * @brief stopsize change request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_baudrate requested stopsize
 * @return actual stopsize that was set
 */
typedef unsigned (*rfc2217_on_stopsize_t)(void *ctx, unsigned requested_stopsize);

/**
 * @brief control signal change request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_control requested control signals
 * @return actual control signals that were set
 */
typedef rfc2217_control_t (*rfc2217_on_control_t)(void *ctx, rfc2217_control_t requested_control);

/**
 * @brief buffer purge request callback
 *
 * @param ctx context pointer passed to rfc2217_server_create
 * @param requested_purge requested buffer purge
 * @return actual buffer purge that was performed
 */
typedef rfc2217_purge_t (*rfc2217_on_purge_t)(void *ctx, rfc2217_purge_t requested_purge);

/**
 * @brief callback on client connection
 * @param ctx context pointer passed to rfc2217_server_create
 */
typedef void (*rfc2217_on_client_connected_t)(void *ctx);

/**
 * @brief callback on client disconnection
 * @param ctx context pointer passed to rfc2217_server_create
 */
typedef void (*rfc2217_on_client_disconnected_t)(void *ctx);

/**
 * @brief callback on data received from client
 * @param ctx context pointer passed to rfc2217_server_create
 * @param data pointer to received data
 * @param len length of received data
 */
typedef void (*rfc2217_on_data_received_t)(void *ctx, const uint8_t *data, size_t len);

/**
 * @brief RFC2217 server configuration
 */
typedef struct {
    void *ctx;  //!< context pointer passed to callbacks
    rfc2217_on_client_connected_t on_client_connected;  //!< callback called when client connects
    rfc2217_on_client_disconnected_t on_client_disconnected;    //!< callback called when client disconnects
    rfc2217_on_baudrate_t on_baudrate;  //!< callback called when client requests baudrate change
	rfc2217_on_datasize_t on_datasize;	//!< callback called when client requests datasize change
	rfc2217_on_datasize_t on_parity;	//!< callback called when client requests parity change
	rfc2217_on_datasize_t on_stopsize;	//!< callback called when client requests stopsize change
    rfc2217_on_control_t on_control;    //!< callback called when client requests control signal change
    rfc2217_on_purge_t on_purge;    //!< callback called when client requests buffer purge
    rfc2217_on_data_received_t on_data_received;    //!< callback called when data is received from client
    unsigned port;              //!< TCP port to listen on
    unsigned task_stack_size;   //!< server task stack size
    unsigned task_priority;     //!< server task priority
    unsigned task_core_id;      //!< server task core ID
} rfc2217_server_config_t;


/** @brief Create RFC2217 server instance
 *
 * @param config RFC2217 server configuration
 * @param out_server pointer to store created server instance
 * @return 0 on success, negative error code on failure
 */
int rfc2217_server_create(const rfc2217_server_config_t *config, rfc2217_server_t *out_server);

/** @brief Start RFC2217 server
 *
 * @param server RFC2217 server instance
 * @return 0 on success, negative error code on failure
 */
int rfc2217_server_start(rfc2217_server_t server);

/** @brief Send data to client
 *
 * @param server RFC2217 server instance
 * @param data pointer to data to send
 * @param len length of data to send
 * @return 0 on success, negative error code on failure
 */
int rfc2217_server_send_data(rfc2217_server_t server, const uint8_t *data, size_t len);

/** @brief Stop RFC2217 server
 *
 * @param server RFC2217 server instance
 * @return 0 on success, negative error code on failure
 */
int rfc2217_server_stop(rfc2217_server_t server);

/** @brief Destroy RFC2217 server instance
 *
 * @param server RFC2217 server instance
 */
void rfc2217_server_destroy(rfc2217_server_t server);


#ifdef __cplusplus
};
#endif

rfc2217-server/include/rfc2217_server.c:

Slightly improved baudrate support to report consistent format
Added support for data size, parity, stopsize and parity requests

static void process_subnegotiation(rfc2217_server_t server)
{
    ESP_LOGD(TAG, "Processing subnegotiation");
    if (server->suboption[0] != T_COM_PORT_OPTION) {
        ESP_LOGD(TAG, "Unknown subnegotiation: %x", server->suboption[0]);
        return;
    }

    uint8_t subnegotiation = server->suboption[1];
    if (subnegotiation == T_SET_BAUDRATE) {
        uint32_t baudrate = (server->suboption[2] << 24) | (server->suboption[3] << 16) | (server->suboption[4] << 8) | server->suboption[5];
        uint32_t new_baudrate = baudrate;
        if (server->config.on_baudrate) {
            new_baudrate = server->config.on_baudrate(server->config.ctx, baudrate);
	        ESP_LOGI(TAG, "Set baudrate: requested %" PRIu32 ", configured %" PRIu32, baudrate, new_baudrate);
		} else {
	        ESP_LOGW(TAG, "Set baudrate: %" PRIu32 " - not configured, accepting", baudrate);
        }
        uint8_t data[4] = {new_baudrate >> 24, new_baudrate >> 16, new_baudrate >> 8, new_baudrate};
        rfc2217_send_subnegotiation(server, T_SERVER_SET_BAUDRATE, data, 4);
    } else if (subnegotiation == T_SET_DATASIZE) {
        uint8_t datasize = server->suboption[2];
		if (server->config.on_datasize) {
			server->suboption[2] = server->config.on_datasize(server->config.ctx, datasize);
	        ESP_LOGI(TAG, "Set datasize: requested %" PRIu8 ", configured %" PRIu8, datasize, server->suboption[2]);
		} else {
	        ESP_LOGW(TAG, "Set datasize: %" PRIu8 " - not configured, accepting", datasize);
		}
        rfc2217_send_subnegotiation(server, T_SERVER_SET_DATASIZE, &server->suboption[2], 1);
    } else if (subnegotiation == T_SET_PARITY) {
        uint8_t parity = server->suboption[2];
		if (server->config.on_parity) {
			server->suboption[2] = server->config.on_parity(server->config.ctx, parity);
	        ESP_LOGI(TAG, "Set parity: requested %" PRIu8 ", configured %" PRIu8, parity, server->suboption[2]);
		} else {
	        ESP_LOGW(TAG, "Set parity: %" PRIu8 " - not configured, accepting", parity);
		}
        rfc2217_send_subnegotiation(server, T_SERVER_SET_PARITY, &server->suboption[2], 1);
    } else if (subnegotiation == T_SET_STOPSIZE) {
        uint8_t stopsize = server->suboption[2];
		if (server->config.on_stopsize) {
			server->suboption[2] = server->config.on_stopsize(server->config.ctx, stopsize);
	        ESP_LOGI(TAG, "Set stopsize: requested %" PRIu8 ", configured %" PRIu8, stopsize, server->suboption[2]);
		} else {
	        ESP_LOGW(TAG, "Set stopsize: %" PRIu8 " - not configured, accepting", stopsize);
		}
        rfc2217_send_subnegotiation(server, T_SERVER_SET_STOPSIZE, &server->suboption[2], 1);
    } else if (subnegotiation == T_SET_CONTROL) {
        uint8_t control_byte = server->suboption[2];
        rfc2217_control_t control = (rfc2217_control_t)control_byte;
        rfc2217_control_t new_control = control;
        if (server->config.on_control) {
            new_control = server->config.on_control(server->config.ctx, control);
	        ESP_LOGD(TAG, "Set control: requested %d, configured %d", control, new_control);
        } else {
    	    ESP_LOGD(TAG, "Set control: %d - not configured, accepting", control);
		}
        uint8_t data[1] = {new_control};
        rfc2217_send_subnegotiation(server, T_SERVER_SET_CONTROL, data, 1);
    } else if (subnegotiation == T_NOTIFY_LINESTATE) {
        uint8_t linestate = server->suboption[2];
        ESP_LOGD(TAG, "Notify linestate: %d - not supported", linestate);
        rfc2217_send_subnegotiation(server, T_SERVER_NOTIFY_LINESTATE, &server->suboption[2], 1);
    } else if (subnegotiation == T_NOTIFY_MODEMSTATE) {
        uint8_t modemstate = server->suboption[2];
        ESP_LOGD(TAG, "Notify modemstate: %d - not supported", modemstate);
        rfc2217_send_subnegotiation(server, T_SERVER_NOTIFY_MODEMSTATE, &server->suboption[2], 1);
    } else if (subnegotiation == T_PURGE_DATA) {
        uint8_t purge = server->suboption[2];
//why?	rfc2217_send_subnegotiation(server, T_SERVER_PURGE_DATA, &server->suboption[2], 1);
        rfc2217_purge_t purge_type = (rfc2217_purge_t)purge;
        rfc2217_purge_t purge_result;
        if (server->config.on_purge) {
            purge_result = server->config.on_purge(server->config.ctx, purge_type);
	        ESP_LOGD(TAG, "Purge data: requested %d, accepted %d", purge_type, purge_result);
        } else {
			purge_result = purge_type;
	        ESP_LOGD(TAG, "Purge data: %d - not configured, accepting", purge_type);
		}
        uint8_t data[1] = {purge_result};
        rfc2217_send_subnegotiation(server, T_SERVER_PURGE_DATA, data, 1);
    } else {
        ESP_LOGD(TAG, "Unknown subnegotiation: %x", subnegotiation);
    }
}

Q1: Why the duplicated functionality in lines 565 and 573 (original source code)?

You are welcome to include the changed code above directly into your source, no credit or copyright required...

In addition to the above I have changed the UART example to add the required support.
A key item still missing is TX buffer purge and it seems the idf.py RFC2217 support requires it.
Any suggestion on how to achieve that one will be highly appreciated.

The basic functionality is working to reset the device and (sometimes) get it into download mode, but no luck (yet) get any flash operations to work.

#include "protocol_examples_common.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_intr_alloc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_timer.h"

#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "rfc2217_server.h"

// ########################################### Macros ##############################################

#define uartLEVEL_SET		0
#define uartLEVEL_CLEAR		1

#define tRESET_US			50UL	// period to hold in reset state
#define tSAMPLE_US			2000UL	// period to keep IO0 low for MCU to sample download status

// ######################################## Enumerations ###########################################

typedef enum { IDLE, IO0low, RSTlow, RSThi, IO0hi, UnDef, stateNUM } rfc_state_t;
typedef enum { tmrIDLE, tmrRESET, tmrSAMPLE } tmr_state_t;

// ######################################### Structures ############################################

// #################################### Forward declarations #######################################

static void on_connected(void *ctx);
static void on_disconnected(void *ctx);
static void on_data_received(void *ctx, const uint8_t *data, size_t len);
static unsigned on_baudrate(void *ctx, unsigned baudrate);
static unsigned on_datasize(void *ctx, unsigned datasize);
static unsigned on_parity(void *ctx, unsigned parity);
static unsigned on_stopsize(void *ctx, unsigned stopsize);
static unsigned on_purge(void *ctx, unsigned purge_type);
static unsigned on_control(void * ctx, unsigned control);
static void do_download(void *);
static esp_err_t init_uart(void);

// ###################################### Private constants ########################################

const char *TAG = "rfc2217";
char * States[stateNUM] = { "Idle", "DTRset/IO0", "RTSset/EN", "RTSclr/EN", "DTRclr/IO0", "UnDef" };

const esp_timer_create_args_t tmrCfg = {
	.callback = do_download,
	.arg = (void *)&tmrCfg,
	.dispatch_method = ESP_TIMER_TASK,
	.name = "rfc2217",
	.skip_unhandled_events = 0,
};

// ###################################### Private variables ########################################

rfc2217_server_t s_server;
SemaphoreHandle_t s_client_connected;
SemaphoreHandle_t s_client_disconnected;
esp_timer_handle_t tmrHndl;
rfc_state_t State = IDLE;
tmr_state_t tmrStep = tmrIDLE;

unsigned LastCmd;
int8_t PrvDTR = uartLEVEL_CLEAR, CurDTR = uartLEVEL_CLEAR, PrvRTS = uartLEVEL_CLEAR, CurRTS = uartLEVEL_CLEAR;
int64_t tDTRset = 0, tDTRclr = 0, tRTSset = 0, tRTSclr = 0;

// ######################################## Application ############################################

void app_main(void) {
	esp_log_level_set("*", ESP_LOG_INFO);
#if (buildORIG == 1)
	ESP_ERROR_CHECK(nvs_flash_init());
	ESP_ERROR_CHECK(esp_netif_init());
	ESP_ERROR_CHECK(esp_event_loop_create_default());
	ESP_ERROR_CHECK(example_connect());
#else
	int iRV = nvs_flash_init();							// Initialize Non Volatile Storage
	if (iRV == ESP_ERR_NVS_NO_FREE_PAGES || iRV == ESP_ERR_NVS_NEW_VERSION_FOUND) {
		// OTA app partition table has a smaller NVS partition size than the non-OTA
		// partition table. This size mismatch may cause NVS initialization to fail.
		// If this happens, we erase NVS partition and initialize NVS again.
		ESP_ERROR_CHECK(nvs_flash_erase());			
		iRV = nvs_flash_init();
	}
	ESP_ERROR_CHECK(iRV);
	halWL_AIO_Configure();
	halWL_AIO_Connect("irmacos", "Irm@C0$1");
#endif

	ESP_ERROR_CHECK(init_uart());
	s_client_connected = xSemaphoreCreateBinary();
	s_client_disconnected = xSemaphoreCreateBinary();
	rfc2217_server_config_t config = {
		.ctx = NULL,
		.on_client_connected = on_connected,
		.on_client_disconnected = on_disconnected,
		.on_baudrate = on_baudrate,
		.on_datasize = on_datasize,
		.on_parity = on_parity,
		.on_stopsize = on_stopsize,
		.on_control = on_control,
		.on_purge = on_purge,
		.on_data_received = on_data_received,
		.port = CONFIG_UART_A_TNET_PORT,
		.task_stack_size = 4096,
		.task_priority = 5,
		.task_core_id = -1
	};
	ESP_ERROR_CHECK(rfc2217_server_create(&config, &s_server));
	ESP_LOGI(TAG, "Starting RFC2217 server on port %u", config.port);
	ESP_ERROR_CHECK(rfc2217_server_start(s_server));

	while (true) {
		ESP_LOGI(TAG, "Waiting for client to connect");
		xSemaphoreTake(s_client_connected, portMAX_DELAY);

		ESP_LOGI(TAG, "Client connected, starting data transfer");

		while (xSemaphoreTake(s_client_disconnected, 0) != pdTRUE) {
			static uint8_t uart_read_buf[2048];
			int len = uart_read_bytes(CONFIG_UART_A_PORT_NUM, uart_read_buf, sizeof(uart_read_buf), pdMS_TO_TICKS(100));
			if (len > 0) {
				ESP_LOG_BUFFER_HEXDUMP("TX", uart_read_buf, len, 3);
				rfc2217_server_send_data(s_server, uart_read_buf, len);
			}
		}

		ESP_LOGI(TAG, "Client disconnected");
	}
}

static esp_err_t init_uart(void) {
	uart_config_t uart_config = {
		.baud_rate = 115200,
		.data_bits = UART_DATA_8_BITS,
		.parity = UART_PARITY_DISABLE,
		.stop_bits = UART_STOP_BITS_1,
		.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
	};
	const size_t tx_buffer_size = 4096;
	const size_t rx_buffer_size = 4096;
	ESP_RETURN_ON_ERROR(uart_driver_install(CONFIG_UART_A_PORT_NUM, rx_buffer_size, tx_buffer_size, 0, NULL, ESP_INTR_FLAG_LOWMED), TAG, "uart_driver_install failed");
	ESP_RETURN_ON_ERROR(uart_param_config(CONFIG_UART_A_PORT_NUM, &uart_config), TAG, "uart_param_config failed");
	ESP_RETURN_ON_ERROR(uart_set_pin(CONFIG_UART_A_PORT_NUM, CONFIG_UART_A_TX_GPIO, CONFIG_UART_A_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "uart_set_pin failed");
	// configure simulated RTS & DSR pins using GPIOs
	gpio_config_t cfg_dtr_io0 = {
		.pin_bit_mask = (1ULL << CONFIG_UART_A_DTR_GPIO),
		.mode = GPIO_MODE_OUTPUT_OD,
		.pull_up_en = GPIO_PULLUP_DISABLE,
		.pull_down_en = GPIO_PULLDOWN_DISABLE,
		.intr_type = GPIO_INTR_DISABLE,
	};
	ESP_ERROR_CHECK(gpio_config((const gpio_config_t *) &cfg_dtr_io0));
	ESP_ERROR_CHECK(gpio_set_level(CONFIG_UART_A_DTR_GPIO, uartLEVEL_CLEAR));
	gpio_config_t cfg_rts_en = {
		.pin_bit_mask = (1ULL << CONFIG_UART_A_RTS_GPIO),
		.mode = GPIO_MODE_OUTPUT_OD,
		.pull_up_en = GPIO_PULLUP_DISABLE,
		.pull_down_en = GPIO_PULLDOWN_DISABLE,
		.intr_type = GPIO_INTR_DISABLE,
	};
	ESP_ERROR_CHECK(gpio_config((const gpio_config_t *) &cfg_rts_en));
	ESP_ERROR_CHECK(gpio_set_level(CONFIG_UART_A_RTS_GPIO, uartLEVEL_CLEAR));

	ESP_LOGW(TAG, "Creating time %s", tmrCfg.name);
	ESP_ERROR_CHECK(esp_timer_create(&tmrCfg, &tmrHndl));
	return ESP_OK;
}

// ##################################### Server callbacks ##########################################

static void on_connected(void *ctx) { xSemaphoreGive(s_client_connected); }

static void on_disconnected(void *ctx) { xSemaphoreGive(s_client_disconnected); }

static void on_data_received(void *ctx, const uint8_t *data, size_t len) {
	ESP_LOG_BUFFER_HEXDUMP("RX", data, len, 3);
	uart_write_bytes(CONFIG_UART_A_PORT_NUM, data, len); 
}

static unsigned on_baudrate(void *ctx, unsigned baudrate) {
	esp_err_t err = uart_set_baudrate(CONFIG_UART_A_PORT_NUM, baudrate);
	if (err != ESP_OK) {
		ESP_LOGE(TAG, "Failed to set baudrate: %u", baudrate);
		return 0;
	}
	return baudrate;
}

static unsigned on_datasize(void *ctx, unsigned datasize) {
	esp_err_t err = ESP_OK;
	uart_word_length_t eVal = 0;
	if (datasize == 0) {			// get current
		err = uart_get_word_length(CONFIG_UART_A_PORT_NUM, &eVal);
		datasize = eVal + 5;		// convert 0~3 into 5~8
	} else if (datasize >= 5 && datasize <= 8) {
		eVal = datasize - 5;		// adjust telnet to ESP range
		err = uart_set_word_length(CONFIG_UART_A_PORT_NUM, eVal);
	} else {
		err = ESP_ERR_INVALID_ARG;
	}
	if (err != ESP_OK) {
		ESP_LOGE(TAG, "Failed to get/set datasize: %u", datasize);
		return 0;
	}
	return datasize;
}

static unsigned on_parity(void *ctx, unsigned parity) {
	esp_err_t err = ESP_OK;
	uart_parity_t eVal = 0;
	if (parity == 0) {				// get current
		err = uart_get_parity(CONFIG_UART_A_PORT_NUM, &eVal);
		// map ESP range to Telnet value
		parity = (eVal == UART_PARITY_DISABLE) ? 1 : (eVal == UART_PARITY_EVEN) ? 3 : 2;
	} else if (parity >= 1 && parity <= 3) {
		// map Telnet value to ESP range
		eVal = (parity == 1) ? UART_PARITY_DISABLE : (parity == 2) ? UART_PARITY_ODD : UART_PARITY_EVEN;
		err = uart_set_parity(CONFIG_UART_A_PORT_NUM, eVal);
	} else {
		err = ESP_ERR_INVALID_ARG;
	}
	if (err != ESP_OK) {
		ESP_LOGE(TAG, "Failed to get/set parity: %u", parity);
		return 0;
	}
	return parity;
}

static unsigned on_stopsize(void *ctx, unsigned stopsize) {
	esp_err_t err = ESP_OK;
	uart_stop_bits_t eVal = 0;
	if (stopsize == 0) {			// get current
		err = uart_get_stop_bits(CONFIG_UART_A_PORT_NUM, &eVal);
		// map ESP range to Telnet value
		stopsize = (eVal == UART_STOP_BITS_1) ? 1 : (eVal == UART_STOP_BITS_1_5) ? 3 : 2;
	} else if (stopsize >= 1 && stopsize <= 3) {
		// map Telnet value to ESP range
		eVal = (stopsize == 1) ? UART_STOP_BITS_1 : (stopsize == 2) ? UART_STOP_BITS_2 : UART_STOP_BITS_1_5;
		err = uart_set_stop_bits(CONFIG_UART_A_PORT_NUM, eVal);
	} else {
		err = ESP_ERR_INVALID_ARG;
	}
	if (err != ESP_OK) {
		ESP_LOGE(TAG, "Failed to get/set stopsize: %u", stopsize);
		return 0;
	}
	return stopsize;
}

static unsigned on_purge(void *ctx, unsigned purge_type) {
	esp_err_t err = ESP_OK;
	if (purge_type < 1 || purge_type > 3) {				// Invalid option
		err = ESP_ERR_INVALID_ARG;
	} else {

		if (purge_type == 1 || purge_type == 3) {		// Purge RX buffer
			ESP_ERROR_CHECK(uart_flush_input(CONFIG_UART_A_PORT_NUM));
		}
		if (purge_type == 2 || purge_type == 3) {		// Purge TX buffer
			// Add support here
			ESP_LOGE(__FUNCTION__, "Add support for TX buffer purge!!!");
		}
	}
	if (err != ESP_OK) {
		ESP_LOGE(__FUNCTION__, "Failed to perform purge_type: %u", purge_type);
		return 0;
	}
	return purge_type;
}

/**************************************************************************************************
step 1: pull IO0 / DTR low		prepare for download mode
step 2: pull EN / RTS low		start reset procedure
step 3: wait tRESET_US period	reset period
step 4: pull EN / RTS high		end reset period, device start and sense IO0 pin
step 5: wait tSAMPLE_US period	IO0 sampling period
step 5: pull IO0 / DTR high		start download mode 
**************************************************************************************************/

static void set_pin_state(rfc_state_t eRfcState, gpio_num_t pin, uint32_t level, uint32_t dT) {
	State = eRfcState;
	ESP_ERROR_CHECK(gpio_set_level(pin, level));
	if (dT)	esp_timer_start_once(tmrHndl, dT);
	ESP_LOGI(__FUNCTION__, "Step=%d  State=%d  %s=%lu  P=%d  dT=%lu", tmrStep, State, States[State], level, pin, dT);
}

static void do_download(void * arg) {
	if (tmrStep == tmrRESET) {							// tRESET_US expired, allow boot/sample, clear EN
		tmrStep = tmrSAMPLE;
		set_pin_state(State, CONFIG_UART_A_RTS_GPIO, uartLEVEL_CLEAR, tSAMPLE_US);
	} else if (tmrStep == tmrSAMPLE) {					// tSAMPLE_US expired, IO0 sampled, clear IO0
		tmrStep = tmrIDLE;
		set_pin_state(State, CONFIG_UART_A_DTR_GPIO, uartLEVEL_CLEAR, 0);

	} else {
		ESP_LOGE(__FUNCTION__, "Step=%d  State=%d/%s  I'm lost!!!", tmrStep, State, States[State]);
		tmrStep = tmrIDLE;
	}
}

static unsigned on_control(void * ctx, unsigned control) {
	if (control == RFC2217_CONTROL_SET_NO_FLOW_CONTROL) {
		ESP_ERROR_CHECK(uart_set_hw_flow_ctrl(CONFIG_UART_A_PORT_NUM, UART_HW_FLOWCTRL_DISABLE, 0));
		ESP_ERROR_CHECK(uart_set_sw_flow_ctrl(CONFIG_UART_A_PORT_NUM, 0, 0, 0));
		State = IDLE;

	} else if (control == RFC2217_CONTROL_SET_DTR) {
		if (State == IDLE) {
			State = IO0low;
			ESP_LOGI(__FUNCTION__, "Step=%d  State=%d  %s", tmrStep, State, States[State]);
		} else if (State == RSThi) {
			// leave in same State = RSThi;
			ESP_LOGI(__FUNCTION__, "Step=%d  State=%d  %s", tmrStep, State, States[State]);
		} else {
			set_pin_state(UnDef, CONFIG_UART_A_DTR_GPIO, uartLEVEL_SET, 0);
		}

	} else if (control == RFC2217_CONTROL_CLEAR_DTR) {
		if (State == RSThi) {
			State = IO0hi;
			if (tmrStep == tmrIDLE) {
				tmrStep = tmrRESET;
				// Set EN (reset) and IO0 (indicate download)
				set_pin_state(State, CONFIG_UART_A_RTS_GPIO, uartLEVEL_SET, 0);
				// then start timer for next step(s)
				set_pin_state(State, CONFIG_UART_A_DTR_GPIO, uartLEVEL_SET, tRESET_US);
			} else {
				ESP_LOGW(__FUNCTION__, "Step=%d  State=%d  %s  Controls ignored", tmrStep, State, States[State]);
			}
		 } else {
			set_pin_state(IDLE, CONFIG_UART_A_DTR_GPIO, uartLEVEL_CLEAR, 0);
		 }

	} else if (control == RFC2217_CONTROL_SET_RTS) {
		if (State == IO0low) {
			State = RSTlow;
			ESP_LOGI(__FUNCTION__, "Step=%d  State=%d  %s", tmrStep, State, States[State]);
		} else {
			set_pin_state(IDLE, CONFIG_UART_A_RTS_GPIO, uartLEVEL_SET, 0);
		}

	} else if (control == RFC2217_CONTROL_CLEAR_RTS) {
		if (State == RSTlow) {
			State = RSThi;
			ESP_LOGI(__FUNCTION__, "Step=%d  State=%d  %s", tmrStep, State, States[State]);
		 } else {
			set_pin_state(IDLE, CONFIG_UART_A_RTS_GPIO, uartLEVEL_CLEAR, 0);
		 }

	} else {
		State = UnDef;
		ESP_LOGE(__FUNCTION__, "Control %u not supported", control);
		return 0;
	}
	LastCmd = control;
	return control;
}

Have not yet played with different timing delays until I can have the DTR/RTS logic verified.
If you can have a look at the reset logic and maybe also the timing delay values, much appreciated.

Once we have the full functionality working happy to have the code included in your repo.

Thanks

André

@ammaree
Copy link
Author

ammaree commented Jan 14, 2025

Using idf.py -p rfc2177://192.168.0 12:1023 erase-flash

√ irmacs % idf.py -p rfc2217://192.168.0.12:1023 erase-flash                                                                                                                                                                  23:30:49
Executing action: erase-flash
Running esptool.py in directory /Users/andremaree/DevSpace/z-projects/irmacs/build
Executing "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/bin/python /Users/andremaree/DevSpace/z-sdk/v5.4/components/esptool_py/esptool/esptool.py -p rfc2217://192.168.0.12:1023 -b 460800 --before default_reset --after hard_reset --chip esp32 erase_flash"...
esptool.py v4.9.dev3
Serial port rfc2217://192.168.0.12:1023
Connecting...
Device PID identification is only supported on COM and /dev/ serial ports.
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/__main__.py", line 9, in <module>
    esptool._main()
    ~~~~~~~~~~~~~^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/__init__.py", line 1331, in _main
    main()
    ~~~~^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/__init__.py", line 809, in main
    esp = esp or get_default_connected_device(
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        ser_list,
        ^^^^^^^^^
    ...<5 lines>...
        before=args.before,
        ^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/__init__.py", line 1216, in get_default_connected_device
    _esp.connect(before, connect_attempts)
    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/loader.py", line 726, in connect
    last_error = self._connect_attempt(reset_strategy, mode)
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/esptool/loader.py", line 618, in _connect_attempt
    self._port.flushOutput()
    ~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/serial/serialutil.py", line 591, in flushOutput
    self.reset_output_buffer()
    ~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/serial/rfc2217.py", line 659, in reset_output_buffer
    self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/serial/rfc2217.py", line 876, in rfc2217_send_purge
    item.wait(self._network_timeout)  # wait for acknowledge from the server
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/serial/rfc2217.py", line 359, in wait
    if self.is_ready():
       ~~~~~~~~~~~~~^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/serial/rfc2217.py", line 345, in is_ready
    raise ValueError("remote rejected value for option {!r}".format(self.name))
ValueError: remote rejected value for option 'purge'
...............
esptool.py failed with exit code 1, output of the command is in the /Users/andremaree/DevSpace/z-projects/irmacs/build/log/idf_py_stderr_output_59548 and /Users/andremaree/DevSpace/z-projects/irmacs/build/log/idf_py_stdout_output_59548

Using idf.py -p rfc2177://192.168.0 12:1023 monitor when I try to do c-T c-A to do app-flash, get following output

Traceback (most recent call last):
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/pyparsing/core.py", line 846, in _parseNoCache
    loc, tokens = self.parseImpl(instring, pre_loc, do_actions)
                  ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/pyparsing/core.py", line 2466, in parseImpl
    if instring[loc] == self.firstMatchChar and instring.startswith(
       ~~~~~~~~^^^^^
IndexError: string index out of range

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/andremaree/DevSpace/z-sdk/v5.4/tools/ldgen/ldgen/entity.py", line 134, in add_sections_info
    results = parser.parseString(first_line, parseAll=True)
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/pyparsing/util.py", line 377, in _inner
    return fn(self, *args, **kwargs)
  File "/Users/andremaree/.espressif/python_env/idf5.5_py3.13_env/lib/python3.13/site-packages/pyparsing/core.py", line 1212, in parse_string
    raise exc.with_traceback(None)
pyparsing.exceptions.ParseException: Expected 'In archive', found end of text  (at char 1), (line:2, col:1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/andremaree/DevSpace/z-sdk/v5.4/tools/ldgen/ldgen.py", line 176, in <module>
    main()
    ~~~~^^
  File "/Users/andremaree/DevSpace/z-sdk/v5.4/tools/ldgen/ldgen.py", line 133, in main
    sections_infos.add_sections_info(dump)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/Users/andremaree/DevSpace/z-sdk/v5.4/tools/ldgen/ldgen/entity.py", line 136, in add_sections_info
    raise ParseException('Parsing sections info for library ' + sections_info_dump.name + ' failed. ' + p.msg)
pyparsing.exceptions.ParseException: Parsing sections info for library /Users/andremaree/DevSpace/z-projects/irmacs/build/esp-idf/xtensa/libxtensa.a failed. Expected 'In archive'  (at char 0), (line:1, col:1)
ninja: build stopped: subcommand failed.
ninja failed with exit code 1, output of the command is in the /Users/andremaree/DevSpace/z-projects/irmacs/build/log/idf_py_stderr_output_59396 and /Users/andremaree/DevSpace/z-projects/irmacs/build/log/idf_py_stdout_output_59396
--- Build failed
--- Press Ctrl+] to exit monitor.
--- Press Ctrl+F to build & flash project.
--- Press Ctrl+A to build & flash app.
--- Press any other key to resume monitor (resets target).

@ammaree
Copy link
Author

ammaree commented Jan 15, 2025

Just another comment.

I am ONLY trying to make this work with target devices without any transistor drivers, directly controlling EN and IO0 with GPIO's from the host device.

Should it become a requirement at a later stage to support target devices through a USB (to UART) interface the required timing adjustments can be made at that stage.

For an example see this device but there are many more...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants