HTTP GET and POST with an Arduino UNO and an ENC28J60

This is purely for nostalgia’s sake: An HTTP GET request for an Arduino UNO with an ENC28J60.

#include <UIPEthernet.h>

EthernetClient client;
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};

void setup() {
  Serial.begin(9600);
  char server[] = "www.someserver.com";
  if(Ethernet.begin(mac) == 0){
    Serial.println("Failed to configure Ethernet using DHCP");
    while(1); //do nothing if ethernet failed to start
  }
  if (client.connect(server, 80)){
      Serial.println("Connected to server");
      client.println("GET / HTTP/1.1");
      client.println("Host: someserver.com");
      client.println();
  } else {
      Serial.println("Connection to server failed");
  }
}

void loop() {  
  while(client.connected()) {
    if(client.available()) {
      char c = client.read();
      if(c != -1) { // Check if read was successful
        Serial.print(c);  
      } else {
        Serial.println("Read failed");
        break;
      }
    }
  }

  if (!client.connected()) {
    Serial.println("Client disconnected");
    client.stop();
  }
}

HTTP POST request:

#include <UIPEthernet.h>

EthernetClient client;
uint8_t mac[6] = {0x00,0x01,0x02,0x03,0x04,0x05};
char server[] = "www.someserver.com";

void setup() {
  Serial.begin(9600);

  if(Ethernet.begin(mac) == 0){
    Serial.println("Failed to configure Ethernet using DHCP");
    while(1); // do nothing if ethernet failed to start
  }

  String data = preparePostData();
  if (client.connect(server, 80)){
    sendPostRequest(data);
  } else {
    Serial.println("Connection to server failed");
  }
}

void loop() {  
  while(client.connected()) {
    if(client.available()) {
      char c = client.read();
      if(c != -1) {
        Serial.print(c);  
      } else {
        Serial.println("Read failed");
        break;
      }
    }
  }

  if (!client.connected()) {
    Serial.println("Client disconnected");
    client.stop();
  }
}

String preparePostData() {
  String data = "sensor1=";
  data += analogRead(A0);
  data += "&sensor2=";
  data += analogRead(A1);
  return data;
}

void sendPostRequest(String data) {
  Serial.println("Connected to server");
  client.println("POST / HTTP/1.1");
  client.println("Host: someserver.com");
  client.println("Content-Type: application/x-www-form-urlencoded");
  client.print("Content-Length: ");
  client.println(data.length());
  client.println();
  client.println(data);
  client.println();
}

Sketch uses 21864 bytes (67%) of program storage space. Maximum is 32256 bytes.
Global variables use 1480 bytes (72%) of dynamic memory, leaving 568 bytes for local variables. Maximum is 2048 bytes.

HTTP-OTA for Self-updating the Filing system

HTTP OTA of the firmware for an ESP8266 is quite common and it is even possible to have the ESP8266 do that automatically when necessary (self-updating), by updating a version number on the upload server.
It is less known that this is also possible for the file system, which maybe necessary if you have files there, such as an html or JS file, or images that need updating. The below code shows how to do that.

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <FS.h>
#include <LittleFS.h> // Use SPIFFS.h if you're using SPIFFS

const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
const char* fileUrl = "http://your-server.com/filesystem.bin"; // URL of the filesystem image
const char* versionUrl = "http://your-server.com/version.txt"; // URL of the version file
const char* currentVersionFile = "/version.txt"; // File to store the current version

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Initialize LittleFS
  if (!LittleFS.begin()) {
    Serial.println("Failed to mount file system");
    return;
  }

  // Check for updates
  if (isUpdateAvailable()) {
    if (updateFilesystem()) {
      Serial.println("Filesystem updated successfully");
    } else {
      Serial.println("Failed to update filesystem");
    }
  } else {
    Serial.println("No update available");
  }
}

bool isUpdateAvailable() {
  String currentVersion = readCurrentVersion();
  String newVersion = fetchServerVersion();

  Serial.println("Current version: " + currentVersion);
  Serial.println("Server version: " + newVersion);

  return newVersion > currentVersion;
}

String readCurrentVersion() {
  if (!LittleFS.exists(currentVersionFile)) {
    return "0.0.0"; // Default version if no version file is found
  }

  File file = LittleFS.open(currentVersionFile, "r");
  if (!file) {
    Serial.println("Failed to open version file");
    return "0.0.0";
  }

  String version = file.readStringUntil('\n');
  file.close();
  return version;
}

String fetchServerVersion() {
  HTTPClient http;
  http.begin(versionUrl);
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {
    String version = http.getString();
    version.trim();
    return version;
  } else {
    Serial.printf("Failed to fetch server version, error: %s\n", http.errorToString(httpCode).c_str());
    return "0.0.0";
  }
}

bool updateFilesystem() {
  HTTPClient http;
  http.begin(fileUrl);
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {
    WiFiClient* stream = http.getStreamPtr();
    if (!LittleFS.format()) {
      Serial.println("Failed to format filesystem");
      return false;
    }
    
    File file = LittleFS.open("/filesystem.bin", "w");
    if (!file) {
      Serial.println("Failed to open file for writing");
      return false;
    }

    uint8_t buffer[128] = { 0 };
    int len = stream->available();
    while (len > 0) {
      int bytesRead = stream->readBytes(buffer, sizeof(buffer));
      file.write(buffer, bytesRead);
      len -= bytesRead;
    }
    file.close();

    // Update the current version file
    updateCurrentVersion(fetchServerVersion());

    return true;
  } else {
    Serial.printf("HTTP request failed, error: %s\n", http.errorToString(httpCode).c_str());
    return false;
  }
}

void updateCurrentVersion(String version) {
  File file = LittleFS.open(currentVersionFile, "w");
  if (!file) {
    Serial.println("Failed to open version file for writing");
    return;
  }

  file.println(version);
  file.close();
}

void loop() {
  // Your code here
}

Explanation:

  1. Version Check:
    • isUpdateAvailable() function compares the current version with the server version.
    • readCurrentVersion() reads the current version from the filesystem.
    • fetchServerVersion() downloads the version file from the server.
  2. Filesystem Update:
    • The update is only performed if the server version is newer.
    • updateFilesystem() handles the downloading and writing of the new filesystem image.
    • updateCurrentVersion() updates the current version file after a successful update.

in order to get a ‘filesystem.bin’. You go to ‘Tools-ESP8266LittleFSDataUpload’and click that. You will get the following in your monitor:

That tells you where you can find the filesystem bin file. Copy that file to your uploadserver and give it the name your firmware expects (in this example that is “filesystem.bin”).
Make sure your filesystem contained a file ‘version.txt’ with the new version number. after you put the bin file on your upload server. update the version number in the ‘version.txt’ file that is on your upload server.
In case you are confused: your update server should have 2 files:

  • “version.bin”- this is your compiled filesystem that also should have a (compiled) ‘version.text’ in it with a number of the new version.
  • “version.txt” – a simple text file that contains the new version

Renesas EK-RA2L1 and DA16200 WiFi shield.

The Renesas EK-RA2L1 is a development board for the Renesas R7FA2L1 MCU. It has an Arduino UNO compatible set of headers that fit the DA16200 WiFi Shield. Ironically the UNO cannot manage the DA16200 shield, but the MKR Arduin series can…but they have a different formfactor. Due to its very low power use it is excellent for battery fed IoT projects.

The following program lets the board read a DS18B20 and publish it to Thingspeak

#include "hal_data.h"
#include "r_uart_api.h"
#include "r_gpio_api.h"
#include "r_wdt_api.h"
#include "r_ioport_api.h"
#include "common_utils.h"

// WiFi credentials and ThingSpeak settings
#define WIFI_SSID "your_wifi_ssid"
#define WIFI_PASSWORD "your_wifi_password"
#define THINGSPEAK_API_KEY "your_thingspeak_api_key"
#define THINGSPEAK_CHANNEL_ID "your_thingspeak_channel_id"

// DS18B20 GPIO pin
#define DS18B20_PIN IOPORT_PORT_01_PIN_04

// UART for DA16200
#define UART_CHANNEL 1

static uint8_t rx_buffer[128];
static uint8_t tx_buffer[128];

void uart_callback(uart_callback_args_t *p_args)
{
    // Handle UART events here
}

void setup_wifi()
{
    // Initialize UART for DA16200
    g_uart0.p_api->open(g_uart0.p_ctrl, g_uart0.p_cfg);

    // Connect to WiFi network
    sprintf(tx_buffer, "AT+CWJAP=\"%s\",\"%s\"\r\n", WIFI_SSID, WIFI_PASSWORD);
    g_uart0.p_api->write(g_uart0.p_ctrl, tx_buffer, strlen(tx_buffer));

    // Wait for response
    g_uart0.p_api->read(g_uart0.p_ctrl, rx_buffer, sizeof(rx_buffer));
    while (strstr(rx_buffer, "OK") == NULL);
}

float read_temperature()
{
    // Implement DS18B20 temperature reading
    // (Assume single-wire protocol library functions are available)
    ds18b20_start_conversion(DS18B20_PIN);
    R_BSP_SoftwareDelay(750, BSP_DELAY_UNITS_MILLISECONDS); // Wait for conversion

    float temperature = ds18b20_read_temperature(DS18B20_PIN);
    return temperature;
}

void send_to_thingspeak(float temperature)
{
    // Format the HTTP POST request
    sprintf(tx_buffer, "AT+CIPSTART=\"TCP\",\"api.thingspeak.com\",80\r\n");
    g_uart0.p_api->write(g_uart0.p_ctrl, tx_buffer, strlen(tx_buffer));
    g_uart0.p_api->read(g_uart0.p_ctrl, rx_buffer, sizeof(rx_buffer));
    while (strstr(rx_buffer, "OK") == NULL);

    sprintf(tx_buffer, "POST /update HTTP/1.1\r\nHost: api.thingspeak.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\n\r\napi_key=%s&field1=%.2f\r\n", 
            44 + strlen(THINGSPEAK_API_KEY), THINGSPEAK_API_KEY, temperature);
    g_uart0.p_api->write(g_uart0.p_ctrl, tx_buffer, strlen(tx_buffer));
    g_uart0.p_api->read(g_uart0.p_ctrl, rx_buffer, sizeof(rx_buffer));
}

int main(void)
{
    // Initialize board and peripherals
    bsp_leds_t leds = BSP_BOARD_LEDS;
    for (uint32_t i = 0; i < leds.led_count; i++)
    {
        g_ioport.p_api->pinWrite(leds.p_leds[i], BSP_IO_LEVEL_HIGH);
    }

    // Set up WiFi
    setup_wifi();

    while (1)
    {
        // Read temperature from DS18B20
        float temperature = read_temperature();

        // Send temperature to ThingSpeak
        send_to_thingspeak(temperature);

        // Wait for 15 seconds before sending the next data
        R_BSP_SoftwareDelay(15000, BSP_DELAY_UNITS_MILLISECONDS);
    }
}

void hal_entry(void)
{
    main();
}

void g_hal_init(void)
{
    g_common_init();
}

The libraries hal_data.h, r_uart_api.h, r_gpio_api.h, r_wdt_api.h, r_ioport_api.h, and common_utils.h are part of the Renesas Flexible Software Package (FSP) for Renesas RA family microcontrollers. The development environment for these libraries is typically e² studio or IAR Embedded Workbench for Renesas, which supports the Renesas RA microcontrollers and provides tools for configuring and programming the microcontroller peripherals.

Steps to Set Up the Development Environment

  1. Install e² studio:
    • Download and install e² studio from the Renesas website.
    • Ensure you have the latest version compatible with Renesas RA microcontrollers.
  2. Install FSP:
    • Download the Renesas Flexible Software Package (FSP) from the Renesas website.
    • Install FSP, which includes the HAL (Hardware Abstraction Layer) drivers and other utilities.
  3. Create a New Project:
    • Open e² studio.
    • Create a new project for the EK-RA21L board.
    • Select the appropriate RA microcontroller in the project setup.
  4. Configure the Project:
    • Use the FSP configurator in e² studio to configure the peripherals like UART, GPIO, WDT, and IOPORT.
    • This will generate the necessary configuration files, including hal_data.h.
  5. Add Source Files:
    • Add your application source files to the project.
    • Include the necessary headers like r_uart_api.h, r_gpio_api.h, r_wdt_api.h, r_ioport_api.h, and common_utils.h.
  6. Build and Debug:
    • Build the project using e² studio.
    • Debug the project using the on-board debugger or an external JTAG/SWD debugger.

In a follow up I will be discussing this board a bit more

Designing Battery/Solar Support for a Deep Sleep System with ESP8266

I have a project designed to wake every two hours, gather data, and send it to Firebase or ThingSpeak. This system is supported by both a solar panel and a 750mAh LiPo battery using a power path load-sharing setup. The sensors include a few DS18B20s, set to operate on 8 bits to speed them up. I use a bare ESP8266-12F soldered on a support plate, without an FTDI chip. The goal is to see how long this would remain active. The project box was closed and put in the sun on May 1, 2024. Software updates were done by on-demand self updating OTA1. Here’s an overview of the energy consumption:

Energy Consumption Overview

  • 150mA for 0.5 seconds: When WiFi is active.
  • 50mA for 0.5 seconds: When the ESP8266 is awake but WiFi is off (sensor reading phase).
  • 7μA for the remaining time: During deep sleep.

Over a period of 2 hours, the energy consumption is as follows:

  • Deep Sleep: 7μA * 2 hours = 14μAh
  • WiFi Active: 150mA * 0.5s = 75mAs
  • Sensor Reading: 50mA * 0.5s = 25mAs

Total energy used in 2 hours:

  • 100mAs + 14μAh = 0.014mAh
  • 100mAs = 100/3600 = 0.0278mAh
  • Total: 0.0418mAh or 41.8μAh in 2 hours

With a 500mAh battery, the theoretical duration would be:

  • 500mAh / 41.8μAh = 11962 periods of 2 hours
  • 23924 hours = 998 days ≈ 2 years 8 months 12 days

Considering inefficiencies, if we can use only half the battery capacity:

  • 1 year 4 months 1 week

Solar Panel Contribution

  • Solar Panel Specs: 6V, 2W (i.e. 333mA)
  • Effective Sunlight: Estimated at 500 hours/year due to fixed position.

During these 500 hours, the panel charges the battery and powers the ESP8266. This could extend the battery life by roughly an additional 21 days, leading to an estimated total lifespan of about 1 year and 9 months. This estimate feels optimistic, so practical testing is necessary.
So, is adding a solar panel useful? A typical amateur project will contain a cheap panel, a TP4056 battery charger, and a LiPo battery. Ideally, that setup requires a load-sharing power path. This prevents the battery from being charged by the solar panel while it is also discharging by feeding the ESP8266.

TP4056 Specifications
  ParameterValue
Voltage Supply (Vs)4V0 ~ 8V0
Charge Voltage termination (accuracy)4.2V(1.5%)
Supply current (Rprog=1.2k: 1A chrg)150uA (typ)
Supply current (Chrg ended/ shutdown)55uA (typ)
Ibat (Rprog=1.2k: 1A chrg )1050mA (max)
Ibat (Stand by mode; Vbat = 4.2V)-6uA (max)
Vtrckl(Rprog=1.2k: Vbat:rising)2.9V (typ)
Itrckl (Rprog=1.2k: Vbat<Vtrckl )140mA max)
Vtrhsy(Rprog=1.2k)80mV (typ)
Operating temperature-40°C ~ 85°C
[trckl = Trickle charge, trhsy = Trickle Charge Hysteresis]

          

A load-sharing power path switches between using the solar panel to feed the ESP8266 and charge the battery when there is enough sunshine and switching to the battery for feeding the ESP8266 when there is not enough sunshine. Such a ‘switch’ basically uses two Schottky diodes (though one can be replaced by a pFET), one in the solar panel line and one in the output of the battery (through the TP4056). These Schottky diodes do have a voltage drop.

For example, say you have an LDO voltage regulator with a voltage drop of 100mV and a Schottky diode with a voltage drop of 200mV. You are losing 0.3V of your battery voltage. The minimum voltage for stable operation is 2.5V, but if you already lose 0.3V, the minimum battery voltage at which the ESP8266 will stop working is 2.8V. This might not be a big issue, as ideally, you should not discharge your batteries below 3.3V anyway. Modern TP4056 modules have discharge protection (through a DW01 FET), but that cuts off at 2.5V. This may be okay for some 18650 batteries but not for most LiPo batteries. (More about powerpaths here). The added protection by the FET’s is limited since the TP4056:

  • Stops discharging at voltages below 2.9V; Here trickle charge activates.
     As the DW01A threshold is ~ 2.4V, it will never activate.
  • Stops charging at voltages above 4.2V.
     As the DW01A threshold is  ~ 4.3V, it will never activate.

The only function that will operate is the over current protection and short circuit protection (or fail safe if the TP4056 chip fails – fairly unlikely). These FETs will activate at around 3A

A standard TP4056 module is preprogrammed for a 1Amp current through the usual 1.2kOhm resistor. Obviously that is a bit too much to deliver for the 333mA solar panel and it’s voltage may drop, so we may want to jack it up to a 4 k resistor, giving only a 300mA charge.
Rprog on the breakout board is R3.

Rprog (kΩ)IBAT (mA) (calculated values)
10130 (120)
5250 (240)
4300
3400
2580 (600)
1.66690 (723)
1.5780 (800)
1.33900
1.21000
Program resistor vs charge current2

Any other charge current can be set by changing Rprog according to the formula:

So say you wanted a Charge current of 750mA that would need Rprog​≈1600 Ω (1599,96 Ω to be precise).

There is some discussion on the internet whether a solar panel can be used with the TP4056 module. Andreas Spiess, in a youtube video claims it can, while others say it can’t as the TP4056 “only knows 5 Volt or 0 Volt and nothing in between”. That might be so, but my experience is that it can be used and will charge your battery, but obviously it will only work in bright sunshine, when 5 Volt will come off of your solarpanel.

One thing is clear: a small solar cell may ideally add 3 weeks to the operational period by relieving the battery from its function, but it is difficult to calculate how much it will actually add by putting charge into the battery. Regardless, it may be more beneficial to just add a battery than to use a solar panel.

Monitoring Battery Voltage

Direct Measurement

One common method is using a voltage divider (e.g., 420kΩ over 100kΩ) to connect the battery to the A0 pin of the ESP8266. This approach, however, results in a continuous current draw, increasing energy consumption by about 25%. An alternative is using a FET as a high-side switch to reduce this loss.

Indirect Measurement

Another option is monitoring the supply line voltage using ADC_MODE(ADC_VCC) and ESP.getVcc(), which gives a 10-bit reading of the rail voltage. Since the ESP8266 is powered through a voltage regulator, this voltage should always be 3.3V unless the battery voltage drops below 3.3V plus the regulator’s drop voltage (typically 90-300mV). This method provides an early warning when the battery is not giving adequate power, but one might miss the gradual change preceding it.
An interesting thing i did see here is that the reading of the Vcc was a solid 3.3 Volt, but that each time after a self-update of the firmware, the read voltage would show a dip to 3.01 Volt.

Considerations for Accuracy

The ESP8266’s ADC is not highly accurate, even with 10-bit resolution. Multiple readings can improve accuracy but at the cost of prolonged awake time. It’s more reliable when WiFi is off, which suits deep sleep projects. Note that ADC_MODE(ADC_VCC) is only viable if nothing is connected to the ADC pin. On boards like Wemos and NodeMCU, a voltage divider between A0 and the ADC pin further reduces accuracy.

In conclusion, while the theoretical calculations provide a promising outlook for battery life, I will need to do practical testing to see what is the best solution and to see if some of my assumptions were valid.

Heat

Given that a solar project is bound to be in the sun, heat can become a problem. When the ESP8266 is in rest, heat can come from different sources:

  • direct sunlight on the solarpanel
  • heat generation in the TP4056 as it is charging the battery at (standard) 1 amp and has to dissipate that over a voltage drop of about 1 Volt.3

The image below shows the increase in temperature in a small project box exposed to sunlight (not seldomly going up to 65 degrees celsius, but for now without any ill effects). The only effect that i noticed upon a temperature rise was that the sleep period was getting slightly longer, say 4-6 min on 2 hrs. let’s call it the ESP8266’s siesta. However point of concern remains that 6o degrees is not an ideal temperature to charge a LiPo battery and it is of course under the same circumstances that cause the high temperature, the battery will receive most of it’s charge.

Having a separate solar-panel with a wire going to the box that keeps your electronics and keeping that in the shade, might be an option

Using energy guzzlers

I am only using DS18B20 sensors, but in any given project, some elements may be constant energy users and drain your battery. An example for instance is a voltage divider that is used to check on battery status. Usually those are 420k, leading to an extra load of some 8-10uA. It becomes worse if perhaps you are reading several resistive soil humidity sensors (and of course using an ADS11115 or similar). Often these add about 20 kOhm per channel, which easily adds 840uA for the voltage dividers+150uA for the ADS1115 (though the latter will be reduced to 0.5uA in ‘one shot mode’ which is what you would typically need in a deepsleep application). This can be avoided by feeding those elements from one of the I/O pins of the ESP8266 which can deliver 12mA. It is important then to use a pin that is LOW in sleep state (eg GPIO15).

If for whatever reason you need to use peripherals that need more than 12 mA, it is best to give those a separate regulator with an enable pin, such as the RT9013, the SPX3819, the ME6211 A &H, or XC6204. Obviously those will need 2 capacitors (input and output). usually around 10hf. These will need 0.5 mSec (500microsec) to charge and will need 15.125 nanowatt-hours (nWh) to charge from 0 to 3V3. a good quality capacitor will keep that charge for a while though

  1. This means that I put new software on a server and the ESP8266 on wake up checks to see if there is new software to download. ↩︎
  2. Obviously the datasheet table seems not totally correct: 1.5k gives 800mA, 1.66k gives 723mA, 2k gives 600mA, 5 k gives 240 and 10K 120mA ↩︎
  3. For illustration: In a confined space of 9x5x3 cm (135 cm3), presuming there is no energy loss, one Watt of energy can heat up the air in that box with 6 degrees per second ↩︎

Time info from a remote ESP8266 (thus not connected to serial port)

Recently I have been working on some deepsleep projects and I needed to get some feedback on the actual sleeptime compared to the programmed sleeptime. As this was in an ESP8266 that was not connected to any terminal, i needed another way to check on how long the sleeptime actually was. Now there are various ways to do it: I could for instance send a value to thingspeak on waking up and see the time difference between two values, but that would require adding quite some code that I did not really need.
As, my project on wake up does already a check on possible new software for an OTA, it already makes an HTTP call, so i just wanted to add another http call that just stores the time. so I simply added:

httpClient.begin(client,"http://192.168.1.128/store_time.php"); // Specify the URL
int httpResponseCode = httpClient.GET(); // Make the GET request

and on my server I added a piece of php script:

<?php
// File path to store the CSV
define('CSV_FILE_PATH', 'timestamps.csv');
// Function to capture the current time and store it in a CSV file
function storeCurrentTimeInCSV() {
    // Capture the current date and time
    $currentTime = date('Y-m-d H:i:s');
    
    // Open the CSV file in append mode
    $fileHandle = fopen(CSV_FILE_PATH, 'a');
    
    // Check if the file is writable
    if ($fileHandle !== false) {
        // Write the current time to the CSV file
        fputcsv($fileHandle, [$currentTime]);
        
        // Close the file
        fclose($fileHandle);
        
        // Return a success response
        return json_encode(['status' => 'success', 'message' => 'Time stored successfully']);
    } else {
        // Return an error response if the file is not writable
        return json_encode(['status' => 'error', 'message' => 'Unable to write to the file']);
    }
}
// Call the function and output the result as a JSON response
header('Content-Type: application/json');
echo storeCurrentTimeInCSV();
?>

So, now every time my ESP8266 wakes up, before it checks for updates, it first makes a call to the “store_time.php” routine. the results look like this:

"2024-05-31 22:59:34"
"2024-05-31 23:32:10"
"2024-06-01 00:05:51"
"2024-06-01 00:39:30"
"2024-06-01 01:13:16"
"2024-06-01 01:47:01"
"2024-06-01 02:20:43"
"2024-06-01 02:54:29"
"2024-06-01 03:28:09"
"2024-06-01 04:01:43"
"2024-06-01 04:35:26"
"2024-06-01 05:09:04"
"2024-06-01 05:42:47"
"2024-06-01 06:16:31"
"2024-06-01 06:50:17"
"2024-06-01 07:23:57"
"2024-06-01 07:57:35"
"2024-06-01 08:31:17"
"2024-06-01 09:05:11"
"2024-06-01 09:38:58"
"2024-06-01 09:39:45"

Anyway, it allows me to try several different settings and once I am happy with what i have it is a matter of commenting out 2 lines and put that new program ready for an automated OTA