How to restart task after it crashes?

Hello there, I am using ESP32 microcontroller under ESP-IDF (Espressif IoT Development Framework). It also supports FreeRTOS. I don’t have so much experience with FreeRTOS, I am such a newbie in that.

I have created simple app that is measuring water level height using ultrasonic sensor, that is sent to webserver via HTTP or HTTPS (based on used firmware) POST request. I am using 2 tasks. One is for measuring datas and other is for sending datas to webserver. There is used Queue. Based on available data in buffer that are stored here by measurement task each 5 minutes, POST request task will do request with datas from queue.

But there is problem…
Sometimes (like once per 3 days, or 1 hour) task for POST request is aborted, because during request there is drop from internet connection or similar and some “unsafe” state happens there in task. So, measurement task is still running and adding values to Queue that can handle 20 values. But POST request task died and it is no longer working.

So how to solve this? How can I implement something like: If task is not running or was crashed, run it again?

I was unable to put link because of first post to Github repo, so I will add there source codes manually (not full project included with header files and so on).

HTTP connection sketch:

/*|-----------------------------------------------------------------------------------|*/
/*|Projekt: Hladinomer - HTTP - HC-SR04 / JSN-SR04T / HY-SRF05                        |*/
/*|ESP32 (DevKit, Generic) - ESP-IDF v4.2 (4.0 compatible)                            |*/
/*|Autor: Martin Chlebovec (martinius96)                                              |*/
/*|E-mail: martinius96@gmail.com                                                      |*/
/*|Info k projektu (schéma): https://martinius96.github.io/hladinomer-studna-scripty/ |*/
/*|Testovacie webove rozhranie: http://arduino.clanweb.eu/studna_s_prekladom/         |*/
/*|Revízia: 4. Jun 2021                                                               |*/
/*|-----------------------------------------------------------------------------------|*/

/* HTTP GET Example using plain POSIX sockets

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "ultrasonic.h"
#include "driver/dac.h"

/* Constants that aren't configurable in menuconfig */

#define MAX_DISTANCE_CM 450 // 5m max
#define GPIO_TRIGGER	22
#define GPIO_ECHO	23
// Webserver
#define WEB_SERVER "arduino.clanweb.eu"
#define WEB_PORT "80"


static const char *TAG = "http_request";
static const char *TAG2 = "ultrasonic_measurement";

QueueHandle_t  q=NULL;
static void ultrasonic(void *pvParamters)
{
	ultrasonic_sensor_t sensor = {
		.trigger_pin = GPIO_TRIGGER,
		.echo_pin = GPIO_ECHO
	};

	ultrasonic_init(&sensor);
  uint32_t distance = 0;
    if(q == NULL){
        printf("Queue is not ready \n");
        return;
    }
	while (true) {
  uint32_t avg_distance = 0;
  int index_loop = 1;
  while(index_loop<=10){
		esp_err_t res = ultrasonic_measure_cm(&sensor, MAX_DISTANCE_CM, &distance);
		if (res != ESP_OK) {
			printf("Error: ");
			switch (res) {
				case ESP_ERR_ULTRASONIC_PING:
					printf("Cannot ping (device is in invalid state)\n");
					break;
				case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
					printf("Ping timeout (no device found)\n");
					break;
				case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
					printf("Echo timeout (i.e. distance too big)\n");
					break;
				default:
					printf("%d\n", res);
			}
		} else {
			printf("Measurement %d: %d cm\n", index_loop, distance);
       avg_distance +=  distance;
      index_loop++;
		}
    }
      avg_distance = avg_distance / 10;
      distance  = avg_distance;
    xQueueSend(q,(void *)&distance,(TickType_t )0); // add the counter value to the queue
            for(int countdown = 300; countdown >= 0; countdown--) {
            ESP_LOGI(TAG2, "%d... ", countdown);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
	}
}

static void http_get_task(void *pvParameters)
{
    const struct addrinfo hints = {
        .ai_family = AF_INET,
        .ai_socktype = SOCK_STREAM,
    };
    struct addrinfo *res;
    struct in_addr *addr;
    int s, r;
    char recv_buf[64];
    uint32_t distance;
     if(q == NULL){
        printf("Queue is not ready \n");
        return;
    }
    while(1) {
    xQueueReceive(q,&distance,portMAX_DELAY); 
    char REQUEST [1000];
	  char values [250];
	  sprintf(values, "hodnota=%d&token=123456789", distance);
    sprintf (REQUEST, "POST /studna_s_prekladom/data.php HTTP/1.0\r\nHost: "WEB_SERVER"\r\nUser-Agent: ESP32\r\nConnection: close\r\nContent-Type: application/x-www-form-urlencoded;\r\nContent-Length:%d\r\n\r\n%s\r\n",strlen(values),values);
        int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);

        if(err != 0 || res == NULL) {
            ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }

        /* Code to print the resolved IP.

           Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
        ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));

        s = socket(res->ai_family, res->ai_socktype, 0);
        if(s < 0) {
            ESP_LOGE(TAG, "... Failed to allocate socket.");
            freeaddrinfo(res);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... allocated socket");

        if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
            ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
            close(s);
            freeaddrinfo(res);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }

        ESP_LOGI(TAG, "... connected");
        freeaddrinfo(res);

        if (write(s, REQUEST, strlen(REQUEST)) < 0) {
            ESP_LOGE(TAG, "... socket send failed");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... socket send success");

        struct timeval receiving_timeout;
        receiving_timeout.tv_sec = 5;
        receiving_timeout.tv_usec = 0;
        if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
                sizeof(receiving_timeout)) < 0) {
            ESP_LOGE(TAG, "... failed to set socket receiving timeout");
            close(s);
            vTaskDelay(4000 / portTICK_PERIOD_MS);
            continue;
        }
        ESP_LOGI(TAG, "... set socket receiving timeout success");

        /* Read HTTP response */
        do {
            bzero(recv_buf, sizeof(recv_buf));
            r = read(s, recv_buf, sizeof(recv_buf)-1);
            for(int i = 0; i < r; i++) {
                putchar(recv_buf[i]);
            }
        } while(r > 0);

        ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
        close(s);
        ESP_LOGI(TAG, "Starting again!");
    }
}

void app_main(void)
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
     q=xQueueCreate(20,sizeof(unsigned long));
    if(q != NULL){
        printf("Queue is created\n");
        vTaskDelay(1000/portTICK_PERIOD_MS); //wait for a second
        xTaskCreate(&ultrasonic, "ultrasonic", 2048, NULL, 5, NULL);
        printf("producer task  started\n");
        xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, NULL);
        printf("consumer task  started\n");
    }else{
        printf("Queue creation failed");
    }    

}

HTTPS connection sketch:

/*|-----------------------------------------------------------------------------------|*/
/*|Projekt: Hladinomer - HTTPS - HC-SR04 / JSN-SR04T / HY-SRF05                       |*/
/*|ESP32 (DevKit, Generic) - ESP-IDF v4.3.X                                           |*/
/*|Autor: Martin Chlebovec (martinius96)                                              |*/
/*|E-mail: martinius96@gmail.com                                                      |*/
/*|Info k projektu (schéma): https://martinius96.github.io/hladinomer-studna-scripty/ |*/
/*|Testovacie webove rozhranie HTTPS: https://hladinomer.000webhostapp.com/           |*/
/*|Revízia: 20. Január 2022                                                           |*/
/*|-----------------------------------------------------------------------------------|*/

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include "esp_netif.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/esp_debug.h"
#include "mbedtls/ssl.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/error.h"
#include "mbedtls/certs.h"
#include "esp_crt_bundle.h"

#include "ultrasonic.h"
#include "driver/dac.h"

/* Constants that aren't configurable in menuconfig */

#define MAX_DISTANCE_CM 450 // 5m max
#define GPIO_TRIGGER	22
#define GPIO_ECHO	23
// Webserver
/* Constants that aren't configurable in menuconfig */
#define WEB_SERVER "hladinomer.000webhostapp.com"
#define WEB_PORT "443"


static const char *TAG = "https_request";
static const char *TAG2 = "ultrasonic_measurement";

QueueHandle_t  q=NULL;
static void ultrasonic(void *pvParamters)
{
	ultrasonic_sensor_t sensor = {
		.trigger_pin = GPIO_TRIGGER,
		.echo_pin = GPIO_ECHO
	};

	ultrasonic_init(&sensor);
  uint32_t distance = 0;
    if(q == NULL){
        printf("Queue is not ready \n");
        return;
    }
	while (true) {
  uint32_t avg_distance = 0;
  int index_loop = 1;
  while(index_loop<=10){
		esp_err_t res = ultrasonic_measure_cm(&sensor, MAX_DISTANCE_CM, &distance);
		if (res != ESP_OK) {
			printf("Error: ");
			switch (res) {
				case ESP_ERR_ULTRASONIC_PING:
					printf("Cannot ping (device is in invalid state)\n");
					break;
				case ESP_ERR_ULTRASONIC_PING_TIMEOUT:
					printf("Ping timeout (no device found)\n");
					break;
				case ESP_ERR_ULTRASONIC_ECHO_TIMEOUT:
					printf("Echo timeout (i.e. distance too big)\n");
					break;
				default:
					printf("%d\n", res);
			}
		} else {
			printf("Measurement %d: %d cm\n", index_loop, distance);
       avg_distance +=  distance;
      index_loop++;
		}
    }
    esp_err_t res = ultrasonic_measure_cm(&sensor, MAX_DISTANCE_CM, &distance);
    if (res == ESP_OK) {
      avg_distance = avg_distance / 10;
      distance  = avg_distance;
      xQueueSend(q,(void *)&distance,(TickType_t )0); // add the value to the queue
    }
            for(int countdown = 300; countdown >= 0; countdown--) {
            ESP_LOGI(TAG2, "%d... ", countdown);
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
	}
}

static void https_get_task(void *pvParameters)
{
     uint32_t distance;
     if(q == NULL){
        printf("Queue is not ready \n");
        return;
    }
    char buf[512];
    int ret, flags, len;

    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_ssl_context ssl;
    mbedtls_x509_crt cacert;
    mbedtls_ssl_config conf;
    mbedtls_net_context server_fd;

    mbedtls_ssl_init(&ssl);
    mbedtls_x509_crt_init(&cacert);
    mbedtls_ctr_drbg_init(&ctr_drbg);
    ESP_LOGI(TAG, "Seeding the random number generator");

    mbedtls_ssl_config_init(&conf);

    mbedtls_entropy_init(&entropy);
    if((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
                                    NULL, 0)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret);
        abort();
    }

    ESP_LOGI(TAG, "Attaching the certificate bundle...");

    ret = esp_crt_bundle_attach(&conf);

    if(ret < 0)
    {
        ESP_LOGE(TAG, "esp_crt_bundle_attach returned -0x%x\n\n", -ret);
        abort();
    }

    ESP_LOGI(TAG, "Setting hostname for TLS session...");

     /* Hostname set here should match CN in server certificate */
    if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
        abort();
    }

    ESP_LOGI(TAG, "Setting up the SSL/TLS structure...");

    if((ret = mbedtls_ssl_config_defaults(&conf,
                                          MBEDTLS_SSL_IS_CLIENT,
                                          MBEDTLS_SSL_TRANSPORT_STREAM,
                                          MBEDTLS_SSL_PRESET_DEFAULT)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret);
        goto exit;
    }

    /* MBEDTLS_SSL_VERIFY_OPTIONAL is bad for security, in this example it will print
       a warning if CA verification fails but it will continue to connect.

       You should consider using MBEDTLS_SSL_VERIFY_REQUIRED in your own code.
    */
    mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
    mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
    mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
#ifdef CONFIG_MBEDTLS_DEBUG
    mbedtls_esp_enable_debug_log(&conf, CONFIG_MBEDTLS_DEBUG_LEVEL);
#endif

    if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0)
    {
        ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
        goto exit;
    }

    while(1) {
  xQueueReceive(q,&distance,portMAX_DELAY); 
  char REQUEST [1000];
	 char values [250];
	 sprintf(values, "hodnota=%d&token=123456789", distance);
    sprintf (REQUEST, "POST /data.php HTTP/1.0\r\nHost: "WEB_SERVER"\r\nUser-Agent: ESP32\r\nConnection: close\r\nContent-Type: application/x-www-form-urlencoded;\r\nContent-Length:%d\r\n\r\n%s\r\n",strlen(values),values); 
        mbedtls_net_init(&server_fd);

        ESP_LOGI(TAG, "Connecting to %s:%s...", WEB_SERVER, WEB_PORT);

        if ((ret = mbedtls_net_connect(&server_fd, WEB_SERVER,
                                      WEB_PORT, MBEDTLS_NET_PROTO_TCP)) != 0)
        {
            ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret);
            goto exit;
        }

        ESP_LOGI(TAG, "Connected.");

        mbedtls_ssl_set_bio(&ssl, &server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);

        ESP_LOGI(TAG, "Performing the SSL/TLS handshake...");

        while ((ret = mbedtls_ssl_handshake(&ssl)) != 0)
        {
            if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
            {
                ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
                goto exit;
            }
        }

        ESP_LOGI(TAG, "Verifying peer X.509 certificate...");

        if ((flags = mbedtls_ssl_get_verify_result(&ssl)) != 0)
        {
            /* In real life, we probably want to close connection if ret != 0 */
            ESP_LOGW(TAG, "Failed to verify peer certificate!");
            bzero(buf, sizeof(buf));
            mbedtls_x509_crt_verify_info(buf, sizeof(buf), "  ! ", flags);
            ESP_LOGW(TAG, "verification info: %s", buf);
        }
        else {
            ESP_LOGI(TAG, "Certificate verified.");
        }

        ESP_LOGI(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl));

        ESP_LOGI(TAG, "Writing HTTP request...");

        size_t written_bytes = 0;
        do {
            ret = mbedtls_ssl_write(&ssl,
                                    (unsigned char *)REQUEST + written_bytes,
                                    strlen(REQUEST) - written_bytes);
            if (ret >= 0) {
                ESP_LOGI(TAG, "%d bytes written", ret);
                written_bytes += ret;
            } else if (ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret != MBEDTLS_ERR_SSL_WANT_READ) {
                ESP_LOGE(TAG, "mbedtls_ssl_write returned -0x%x", -ret);
                goto exit;
            }
        } while(written_bytes < strlen(REQUEST));

        ESP_LOGI(TAG, "Reading HTTP response...");

        do
        {
            len = sizeof(buf) - 1;
            bzero(buf, sizeof(buf));
            ret = mbedtls_ssl_read(&ssl, (unsigned char *)buf, len);

            if(ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
                continue;

            if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
                ret = 0;
                break;
            }

            if(ret < 0)
            {
                ESP_LOGE(TAG, "mbedtls_ssl_read returned -0x%x", -ret);
                break;
            }

            if(ret == 0)
            {
                ESP_LOGI(TAG, "connection closed");
                break;
            }

            len = ret;
            ESP_LOGD(TAG, "%d bytes read", len);
            /* Print response directly to stdout as it is read */
            for(int i = 0; i < len; i++) {
                putchar(buf[i]);
            }
        } while(1);

        mbedtls_ssl_close_notify(&ssl);

    exit:
        mbedtls_ssl_session_reset(&ssl);
        mbedtls_net_free(&server_fd);

        if(ret != 0)
        {
            mbedtls_strerror(ret, buf, 100);
            ESP_LOGE(TAG, "Last error was: -0x%x - %s", -ret, buf);
        }

        putchar('\n'); // JSON output doesn't have a newline at end

        static int request_count;
        ESP_LOGI(TAG, "Completed %d requests", ++request_count);
        ESP_LOGI(TAG, "Starting again!");
    }
}

void app_main(void)
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
    ESP_ERROR_CHECK(example_connect());
     q=xQueueCreate(20,sizeof(unsigned long));
    if(q != NULL){
        printf("Queue is created\n");
        vTaskDelay(1000/portTICK_PERIOD_MS); //wait for a second
        xTaskCreate(&ultrasonic, "ultrasonic", 2048, NULL, 5, NULL);
        printf("Measurement task  started\n");
        xTaskCreate(&https_get_task, "https_get_task", 8192, NULL, 5, NULL);
        printf("HTTPS socket task  started\n");
    }else{
        printf("Queue creation failed");
    }  

}

Code of HTTP or HTTPS task are examples codes from ESP-IDF. It was originally designed for GET requested, but I changed it to POST request for my purposes, so basically there was changed payload, added header application/x-www-form-urlencoded, calculated and added content-length… Also code for ultrasonic task I was using from Github example of some user. And I added inter-task communication using that Queue.

Hi there and welcome to the forum,

your comm task should never “die” or “crash.” It should always react in a prediceable fashion and recover from scenarions like network disturbances whcih are bound to happen sooner or later. Normally there are timeout and retry conditions that return your task(s) to a defined state. If they don’t, you must find the places that don’t and and repair them.

In “deeply embedded systems,” it is pretty much impssible to bring a disturbed system back to operational by “repairing” individual tasks because all tasks are generally very closely intertwined. In other words: If one somponent has a problem, generally the entire system becomes inoperational. Normally this is handled via soft- or hardware watch dogs that enforce a reset, but the preferred strategy is to track down the failure and ensure that it is either fixed or all tasks/components respond to the failure orderly.

Does that make sense?

Thanks for reply,
yes, it does make sense…

I tried to monitor whole UART output of microcontroller for more than day… And no crash of task happened. There were two errors total that happened in HTTPS task, but it didn’t abort task. At next run (after 5 minutes) it normally worked and datas were sent to server without problem. So only in two cases HTTPS request was not created.

https_request: mbedtls_ssl_handshake returned -0x7280
https_request: Last error was: -0x7280 - SSL - The connection indicated an EOF
.
.
https_request: mbedtls_net_connect returned -44
https_request: Last error was: -0x44 - UNKNOWN ERROR CODE (0044)e[0m

Also there were more event related outputs about wifi. Sometimes 2 outputs like below, sometimes 10 with fluctuating states between 3,1 and 3,0 I understand to these outputs that, ESP32 is running in STA (STATION) mode on Channel 3, where WiFi is broadcasted by AP in network. Second parameter 0 or 1 based on ESP-IDF documentation is secondary channel. I dont have idea why still secondary channel was changed.

wifi:new:<3,0>, old:<3,1>, ap:<255,255>, sta:<3,0>, prof:1
wifi:new:<3,1>, old:<3,0>, ap:<255,255>, sta:<3,1>, prof:1

Successful HTTPS connection after value in Queue (reduced prints to UART, no HTTP header or payload):

Adding measurement to Queue 26 cm
https_request: Connected.
esp-x509-crt-bundle: Certificate validated
https_request: connection closed

So I didn’t figure out what should done that crashes that happened earlier.

First 14 hours of UART output (sry i cant put link):
pastebin(dot)com/Hf9L0Bw1

yes, I keep pointing out that serial output monitoring is prone to the Heisenberg effect; it changes the runtime behavior so drastically that some problems stop occurring and others arise.

If you want to stick to serial monitoring, reduce your output to single character (or bare minimal) strings to decode posthumously; that’ll increase your chances of approaching the non-debug runtime behavior. Or, MUCH better, use “silent” (in memory) monitoring and analyze the monitor buffer after your “crash” occurred.

Also, be more precise about what you mean by “crashing.” A “crash” is normally a faults that halts the entire system. Apparently that is not the case in your setup, so if you want support, be as precise as possible when you describe yur problems. Most people here are glad to herlp, but you have to present the information so we don’t need to decipher lengthily what your peoblem is in the first place.

1 Like